<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-603208590136948311</id><updated>2026-10-17T01:04:05.899-07:00</updated><category term="Python"/><category term="C#"/><category term="ArcGIS Server"/><category term="Flex"/><category term="ArcObjects"/><category term="Server Object Extensions"/><category term="ESRI Developer Summit"/><category term="Geoprocessing"/><category term="ArcGIS"/><category term="ArcMap"/><category term="JavaScript"/><category term="Map Tiles"/><category term="Offline Mapping"/><category term="PSU"/><category term="Rasters"/><category term="VB"/><category term=".NET"/><category term="AIR"/><category term="AS3"/><category term="Algorithms"/><category term="Arrays"/><category term="Bing"/><category term="COM"/><category term="Clean Code"/><category term="Clustering"/><category term="Design Patterns"/><category term="Developer Interviews"/><category term="Django"/><category term="ESRI Flex API"/><category term="Flash"/><category term="Geometry"/><category term="Google"/><category term="Imaging"/><category term="Mobile"/><category term="Nokia"/><category term="PIL"/><category term="Pixel Bender"/><category term="Presentations"/><category term="PyTables"/><category term="SQLite"/><category term="Sorting"/><category term="Spatial Analysis"/><category term="Sublime Text 2"/><category term="War"/><category term="Where Camp"/><category term="Yahoo"/><category term="arcpy"/><category term="fuzzy"/><title type='text'>bcdc spatial</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default?redirect=false'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default?start-index=26&amp;max-results=25&amp;redirect=false'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>31</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-2374256888720139802</id><published>2013-05-24T18:59:00.001-07:00</published><updated>2013-05-24T18:59:18.733-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Developer Interviews"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><title type='text'>Interesting Tim Peters interview...</title><content type='html'>&lt;br /&gt;
Really enjoyed seeing the face behind the developer...&lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;text-align: center;&quot;&gt;
&lt;br /&gt;&lt;iframe allowfullscreen=&quot;&quot; frameborder=&quot;0&quot; height=&quot;360&quot; src=&quot;http://www.youtube.com/embed/1wAOy88WxmY&quot; width=&quot;480&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;br /&gt;
Tim Peters (core developer on CPython) discusses the early days of Python language development including meeting Guido, and porting Python to 64bit.&amp;nbsp; Additonal topics include:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Why people are leaving compiler development.&lt;/li&gt;
&lt;li&gt;Constraints of free development time&lt;/li&gt;
&lt;li&gt;Sprint-based development&lt;/li&gt;
&lt;li&gt;Talks about new language features&lt;/li&gt;
&lt;li&gt;Parallelism and processes&lt;/li&gt;
&lt;/ul&gt;
</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/2374256888720139802/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2013/05/interesting-tim-peters-interview.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/2374256888720139802'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/2374256888720139802'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2013/05/interesting-tim-peters-interview.html' title='Interesting Tim Peters interview...'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img.youtube.com/vi/1wAOy88WxmY/default.jpg" height="72" width="72"/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-1711733325667735940</id><published>2013-03-25T23:06:00.000-07:00</published><updated>2013-04-01T08:09:33.507-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Clustering"/><category scheme="http://www.blogger.com/atom/ns#" term="ESRI Developer Summit"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><title type='text'>Point Clustering w/ ArcGIS + Python</title><content type='html'>&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwLi4dB6_YtcPWbnjou5rixH3g7iI29QBB4rCZmKyQaqL0ygzdGvywKLLYPgRd23zcfng5ZDcQ1yYz0g63woH5W5z0jj2vz8yuZFsQhsr114wxlStrGTituI8qggiedQZUE7wFOBga4nK8/s1600/student_teacher_ratio.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;308&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwLi4dB6_YtcPWbnjou5rixH3g7iI29QBB4rCZmKyQaqL0ygzdGvywKLLYPgRd23zcfng5ZDcQ1yYz0g63woH5W5z0jj2vz8yuZFsQhsr114wxlStrGTituI8qggiedQZUE7wFOBga4nK8/s640/student_teacher_ratio.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
Here is a quick example of clustering an arcgis feature class of points.&amp;nbsp; &lt;u&gt;&lt;b&gt;WARNING:&lt;/b&gt;&lt;/u&gt; This is more of a &quot;gist&quot; than a production ready version.&amp;nbsp; I am releasing it to support my presentation at the Esri Developer Summit.&amp;nbsp; Come back soon and it should be updated with additional error handling, much much more efficient use of the tools.&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;import json
import multiprocessing
import os
import unittest

from collections import defaultdict

import arcpy

def cluster_worker(sourcePointFeatureClass, outputDirectory, levelInfos=None, whereClause=None, workerName=None, includeLevels=None):
    print &#39;.rectify_worker({})&#39;.format(workerName)
    arcpy.env.overwriteOutput = True
    outputGeodatabaseName = &quot;temp{}.gdb&quot;.format(workerName)
    outputGeodatabase = os.path.join(outputDirectory, outputGeodatabaseName)
    if arcpy.Exists(outputGeodatabase):
        arcpy.Delete_management(outputGeodatabase)
    arcpy.CreateFileGDB_management(outputDirectory, outputGeodatabaseName)
    arcpy.env.workspace = outputGeodatabase

    points_feature_layer = &#39;temp_points&#39;
    points_feature_class = &#39;source_points&#39;
    
    arcpy.MakeFeatureLayer_management(sourcePointFeatureClass, points_feature_layer, whereClause)
    arcpy.CopyFeatures_management(points_feature_layer, points_feature_class)

    level_infos_json = &quot;&quot;&quot;{&quot;origin&quot;:{&quot;x&quot;:-20037508.342787,&quot;y&quot;:20037508.342787},&quot;spatialReference&quot;:{&quot;wkid&quot;:102100},&quot;lods&quot;:[{&quot;level&quot;:0,&quot;resolution&quot;:156543.033928,&quot;scale&quot;:591657527.591555},{&quot;level&quot;:1,&quot;resolution&quot;:78271.5169639999,&quot;scale&quot;:295828763.795777},{&quot;level&quot;:2,&quot;resolution&quot;:39135.7584820001,&quot;scale&quot;:147914381.897889},{&quot;level&quot;:3,&quot;resolution&quot;:19567.8792409999,&quot;scale&quot;:73957190.948944},{&quot;level&quot;:4,&quot;resolution&quot;:9783.93962049996,&quot;scale&quot;:36978595.474472},{&quot;level&quot;:5,&quot;resolution&quot;:4891.96981024998,&quot;scale&quot;:18489297.737236},{&quot;level&quot;:6,&quot;resolution&quot;:2445.98490512499,&quot;scale&quot;:9244648.868618},{&quot;level&quot;:7,&quot;resolution&quot;:1222.99245256249,&quot;scale&quot;:4622324.434309},{&quot;level&quot;:8,&quot;resolution&quot;:611.49622628138,&quot;scale&quot;:2311162.217155},{&quot;level&quot;:9,&quot;resolution&quot;:305.748113140558,&quot;scale&quot;:1155581.108577},{&quot;level&quot;:10,&quot;resolution&quot;:152.874056570411,&quot;scale&quot;:577790.554289},{&quot;level&quot;:11,&quot;resolution&quot;:76.4370282850732,&quot;scale&quot;:288895.277144},{&quot;level&quot;:12,&quot;resolution&quot;:38.2185141425366,&quot;scale&quot;:144447.638572},{&quot;level&quot;:13,&quot;resolution&quot;:19.1092570712683,&quot;scale&quot;:72223.819286},{&quot;level&quot;:14,&quot;resolution&quot;:9.55462853563415,&quot;scale&quot;:36111.909643},{&quot;level&quot;:15,&quot;resolution&quot;:4.77731426794937,&quot;scale&quot;:18055.954822},{&quot;level&quot;:16,&quot;resolution&quot;:2.38865713397468,&quot;scale&quot;:9027.977411},{&quot;level&quot;:17,&quot;resolution&quot;:1.19432856685505,&quot;scale&quot;:4513.988705},{&quot;level&quot;:18,&quot;resolution&quot;:0.597164283559817,&quot;scale&quot;:2256.994353},{&quot;level&quot;:19,&quot;resolution&quot;:0.298582141647617,&quot;scale&quot;:1128.497176}],&quot;units&quot;:&quot;esriMeters&quot;}&quot;&quot;&quot;
    level_infos = json.loads(unicode(level_infos_json, &#39;latin-1&#39;))
    clustered_feature_classes = []
    for level_info in level_infos[&#39;lods&#39;]:
        if not includeLevels or int(level_info[&#39;level&#39;]) in includeLevels:
            cluster_feature_class = create_cluster_layer(points_feature_class, level_info)
            if cluster_feature_class:
                clustered_feature_classes.append(os.path.join(outputGeodatabase, cluster_feature_class))
    return (clustered_feature_classes, workerName, outputGeodatabase)

def create_cluster_layer(sourcePointFeatureClass, levelInfo):
    level, res = (levelInfo[&#39;level&#39;], levelInfo[&#39;resolution&#39;])

    integrate_name = &#39;integrate_{}&#39;.format(level)
    cluster_name = &#39;clusters_{}&#39;.format(level)
    output_name = &#39;clusters_{}_joined&#39;.format(level)

    if not arcpy.Exists(output_name):
        arcpy.CopyFeatures_management(sourcePointFeatureClass, integrate_name)
        distance = &quot;{} Meters&quot;.format(res * 10)
        try:
            arcpy.Integrate_management(integrate_name, distance)
        except:
            message = arcpy.GetMessage(0)
            if &#39;maximum tolerance exceeded&#39; in message.lower():
                print &#39;maximum tolerance exceeded: {} - {}&#39;.format(integrate_name, distance)
                return None
            else:
                raise

        cluster_features = arcpy.CollectEvents_stats(integrate_name, cluster_name)
        add_attributes(integrate_name, cluster_name, output_name, )

        arcpy.Delete_management(integrate_name)
        arcpy.Delete_management(cluster_name)

        arcpy.AddField_management(output_name, &#39;zoom_level&#39;, &quot;SHORT&quot;)
        expression = &quot;int({})&quot;.format(levelInfo[&#39;level&#39;])
        arcpy.CalculateField_management(output_name, &#39;zoom_level&#39;, expression, &quot;PYTHON&quot;)

    return output_name

def add_attributes(integratedFeatureClass, clustersFeatureClass, outputFeatureClass, mergeNumericFields=[], mergeTextFields=False):
    &#39;&#39;&#39;
    TODO: Add join option for string fields; 
    TOOD: Add customizable fields for summary

    summarizes attributes for clusters
    First - The first input value is used.
    Last -The last input value is used.
    Join -Merge the values together using the delimiter value to separate the values (only valid if the output field type is text).
    Min -The smallest input value is used (only valid if the output field type is numeric).
    Max -The largest input value is used (only valid if the output field type is numeric).
    Mean -The mean is calculated using the input values (only valid if the output field type is numeric).
    Median -The median is calculated using the input values (only valid if the output field type is numeric).
    Sum -The sum is calculated using the input values (only valid if the output field type is numeric).
    StDev -The standard deviation is calculated using the input values (only valid if the output field type is numeric).
    Count -The number of values included in statistical calculations. This counts each value except null values.
    &#39;&#39;&#39;
    fieldmappings = arcpy.FieldMappings()
    fieldmappings.addTable(clustersFeatureClass)

    integrated_fields = arcpy.ListFields(integratedFeatureClass)
    for f in integrated_fields:
        if f.type in [&#39;Integer&#39;, &#39;SmallInteger&#39;, &#39;Double&#39;, &#39;Single&#39;]:
            template = f.name + &#39;_{}&#39;
            for m in [&#39;Min&#39;, &#39;Max&#39;, &#39;Mean&#39;, &#39;Sum&#39;, &#39;Count&#39;, &#39;StDev&#39;, &#39;Median&#39;]:
                fieldmap = arcpy.FieldMap()
                fieldmap.addInputField(integratedFeatureClass, f.name)
                field = fieldmap.outputField
                field.name = template.format(m.lower())
                field.aliasName = template.format(m.lower())
                fieldmap.mergeRule = m
                fieldmap.outputField = field
                fieldmappings.addFieldMap(fieldmap)

    arcpy.SpatialJoin_analysis(clustersFeatureClass, integratedFeatureClass, outputFeatureClass, &quot;#&quot;, &quot;#&quot;, fieldmappings)

def create_membership_where(featureClass, fieldName, values):
    where_membership = &#39;,&#39;.join([&quot;&#39;{}&#39;&quot;.format(v) for v in values])
    return arcpy.AddFieldDelimiters(featureClass, fieldName) + &quot; IN ({})&quot;.format(where_membership)

def get_unique_field_values(featureClass, fieldName, where=None, getValueFunction=None):
    unique_values = []
    with arcpy.da.SearchCursor(featureClass, [fieldName], where) as cursor:
        for r in cursor:
            if getValueFunction:
                unique_values.append(getValueFunction(r[0]))
            else:
                unique_values.append(str(r[0]))
    final_list = list(set(unique_values))
    return final_list

all_results = []
delete_files = []
def cluster_complete(results):
    clustered_feature_classes, workerName, delete_file = results
    print &#39;worker complete({})&#39;.format(workerName)

    all_results += clustered_feature_classes
    delete_files.append(delete_file)

def run_clustering(featureClass, clusterField, outputDirectory, includeLevels=None):
    global all_results
    global delete_files
    arcpy.gp.overwriteOutput = True
    pool = multiprocessing.Pool(multiprocessing.cpu_count() - 1)
    for v in get_unique_field_values(featureClass, clusterField):
        where = create_membership_where(featureClass, clusterField, [v])
        worker_arguments = (featureClass, outputDirectory, None, where, v, includeLevels)
        pool.apply_async(cluster_worker, worker_arguments, callback=cluster_complete)
    pool.close()
    pool.join()

    final_output = featureClass + &#39;_clustered&#39;
    arcpy.CopyFeatures_management(all_results.pop(), final_output)
    arcpy.Append_management(all_results, final_output, &quot;TEST&quot;)

    for d in delete_files:
        arcpy.Delete_management(d)

#=============================================================================================================
# TESTING
#=============================================================================================================
class TestClustering(unittest.TestCase):
    def test_run_clustering(self):
        feature_class = r&#39;D:\data\EPA\FRS_INTERESTS.gdb\FACILITY_INTERESTS_WM&#39;
        clusterField = &#39;STATE_CODE&#39;
        outputDirectory = r&#39;D:\data\EPA&#39;
        levels = range(4, 16)
        run_clustering( feature_class, clusterField, outputDirectory, includeLevels=levels )

    def _add_attributes(self):
        arcpy.gp.overwriteOutput = True
        integrated_feature_class = r&#39;C:\gis_working_directory\tempDE.gdb\integrate_10&#39;
        clusters = r&#39;C:\gis_working_directory\tempDE.gdb\clusters_10&#39;
        outputDirectory = r&#39;C:\gis_working_directory\tempDE.gdb\clusters_10_joined&#39;
        add_attributes(integrated_feature_class, clusters, outputDirectory)

if __name__ == &#39;__main__&#39;:
    unittest.main()

&lt;/code&gt;&lt;/pre&gt;
</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/1711733325667735940/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2013/03/point-clustering-w-arcgis-python.html#comment-form' title='19 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/1711733325667735940'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/1711733325667735940'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2013/03/point-clustering-w-arcgis-python.html' title='Point Clustering w/ ArcGIS + Python'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwLi4dB6_YtcPWbnjou5rixH3g7iI29QBB4rCZmKyQaqL0ygzdGvywKLLYPgRd23zcfng5ZDcQ1yYz0g63woH5W5z0jj2vz8yuZFsQhsr114wxlStrGTituI8qggiedQZUE7wFOBga4nK8/s72-c/student_teacher_ratio.png" height="72" width="72"/><thr:total>19</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-6117987074708525256</id><published>2013-03-20T14:38:00.001-07:00</published><updated>2013-03-20T14:38:58.792-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><category scheme="http://www.blogger.com/atom/ns#" term="Sublime Text 2"/><title type='text'>Great Sublime Text 2 Video</title><content type='html'>This a great resource for learning to Sublime Text 2:&lt;br /&gt;
&lt;iframe allowfullscreen=&quot;&quot; frameborder=&quot;0&quot; height=&quot;315&quot; src=&quot;http://www.youtube.com/embed/TZ-bgcJ6fQo&quot; width=&quot;560&quot;&gt;&lt;/iframe&gt;

&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/6117987074708525256/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2013/03/great-sublime-text-2-video.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/6117987074708525256'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/6117987074708525256'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2013/03/great-sublime-text-2-video.html' title='Great Sublime Text 2 Video'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img.youtube.com/vi/TZ-bgcJ6fQo/default.jpg" height="72" width="72"/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-1055626906422825049</id><published>2013-02-27T08:35:00.000-08:00</published><updated>2013-02-27T08:35:27.650-08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ArcGIS"/><category scheme="http://www.blogger.com/atom/ns#" term="PyTables"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><title type='text'>Converting ArcGIS Feature Class to PyTables</title><content type='html'>Here is an example of converting a point geometry type ArcGIS Feature Class to a &lt;a href=&quot;http://www.pytables.org/moin&quot; target=&quot;_blank&quot;&gt;PyTables&lt;/a&gt; table.  I decided to avoid using the &lt;a href=&quot;http://resources.arcgis.com/en/help/main/10.1/index.html#//018w00000015000000&quot; target=&quot;_blank&quot;&gt;arcpy.da.FeatureClassToNumpyArray&lt;/a&gt;, because I want more flexibility on how data is converted (e.g. geometry types, blobs, rasters, dates). Right now this only handles a point feature class, but I hope to add in other geometry types and make is into a module:


&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;import arcpy
import tables
from tables import *

def feature_class_to_hdf5(featureClass, tableName, groupName=&#39;&#39;, indexFields=[]):
    &#39;&#39;&#39;
    TODO: add polygon and line geometry type support
    TODO: add blob field
    TODO: add raster support
    TODO: add Date logic
    &#39;&#39;&#39;
    try:
        #Validate feature class
        desc = arcpy.Describe(featureClass)
        if desc.dataType != &#39;FeatureClass&#39;:
            raise ValueError(&#39;featureClass must be a featureClass&#39;)
        if desc.featureType != &#39;Simple&#39;:
            raise ValueError(&#39;feature_class_to_table only support simple feature types&#39;)
        if desc.shapeType != &#39;Point&#39;:
            raise ValueError(&#39;sorry&#39;)

        #Construct output location for hdf5
        output_file_name = os.path.splitext(os.path.split(desc.catalogPath)[1])[0]
        if &#39;.gdb&#39; in desc.catalogPath:
            output_directory = os.path.split(os.path.split(desc.catalogPath)[0])[0]
        elif &#39;.shp&#39; in desc.catalogPath:
            output_directory = os.path.split(desc.catalogPath)[0]
        output_hd5_path = os.path.join(output_directory, output_file_name + &#39;.h5&#39;)
        
        #Map ArcGIS Field Types to PyTable Field Types
        fields_dictionary = {}
        fields_dictionary[&#39;OID&#39;] = Int32Col(dflt=1, pos = 0)
        fields_dictionary[&#39;X&#39;] = Float64Col(dflt=1, pos = 1)
        fields_dictionary[&#39;Y&#39;] = Float64Col(dflt=1, pos = 2)

        h5f_field_names = [&#39;OID&#39;, &#39;X&#39;, &#39;Y&#39;]
        arc_field_names = [&#39;OID@&#39;, &#39;SHAPE@X&#39;, &#39;SHAPE@Y&#39;]
        for f in desc.fields:
            if f.type in [&#39;String&#39;,&#39;GUID&#39;]:
                fields_dictionary[str(f.name)] = StringCol(itemsize=f.length, dflt=&quot;n/a&quot;, pos=len(arc_field_names))
            elif f.type == &#39;Single&#39;:
                fields_dictionary[str(f.name)] = Float32Col(dflt=1, pos=len(arc_field_names))
            elif f.type == &#39;Double&#39;:
                fields_dictionary[str(f.name)] = Float64Col(dflt=1, pos=len(arc_field_names))
            elif f.type == &#39;SmallInteger&#39;:
                fields_dictionary[str(f.name)] = Int16Col(dflt=1, pos=len(arc_field_names))
            elif f.type == &#39;Integer&#39;:
                fields_dictionary[str(f.name)] = Int32Col(dflt=1, pos=len(arc_field_names))
            elif f.type == &#39;Date&#39;:
                fields_dictionary[str(f.name)] = Time64Col(dflt=1, pos=len(arc_field_names)) #Needs test...
            else:
                continue
            arc_field_names.append(str(f.name))
            h5f_field_names.append(str(f.name))

        #Create PyTables DescriptionClass
        DescriptionClass = type(tableName, (IsDescription,), fields_dictionary)

        with tables.openFile(output_hd5_path, &#39;w&#39;, title=featureClass) as h5_file:
            filters = Filters(complib=&#39;blosc&#39;, complevel=1)
            h5_feature_class = h5_file.createTable(&#39;/&#39;, tableName, DescriptionClass, filters=filters)
            with arcpy.da.SearchCursor(featureClass, field_names=arc_field_names) as cursor:
                for r in cursor:
                    new_row = h5_feature_class.row
                    for i, fieldName in enumerate(h5f_field_names):
                        try:
                            new_row[fieldName] = r[i]
                        except TypeError as e:
                            if &#39;unicode&#39; in str(e).lower():
                                new_row[fieldName] = unicodedata.normalize(&#39;NFKD&#39;, r[i]).encode(&#39;ascii&#39;,&#39;ignore&#39;)
                            else:
                                raise
                    new_row.append()
            h5_file.flush()

    except Exception as e:
        print e
        raise
&lt;/code&gt;&lt;/pre&gt;
</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/1055626906422825049/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2013/02/converting-arcgis-feature-class-to.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/1055626906422825049'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/1055626906422825049'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2013/02/converting-arcgis-feature-class-to.html' title='Converting ArcGIS Feature Class to PyTables'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-4749088683157832497</id><published>2013-01-09T18:51:00.000-08:00</published><updated>2013-01-09T18:51:04.651-08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="arcpy"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><title type='text'>Funky Arcpy Cursors - functional programming examples</title><content type='html'>Python has great functional programming features with lambda expressions and first-class functions.  There are many ways to cook a turkey, but here are some examples which rely heavily on a functional approach.&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
Map Functions to Feature Class Rows&amp;nbsp;&lt;/h4&gt;
This example simply wraps a search cursor and executes functions. Not super helpful, but it shows the functional mindset and the use of functions as input parameters &lt;br /&gt;
&lt;br /&gt;

&lt;pre style=&quot;font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%&quot;&gt;&lt;code&gt;def mapFunctionsToFeatureClassRows(featureClass, functionList):
    rows = arcpy.SearchCursor(inputFeatureClass, where)
    for r in rows:
        for f in functionList:
            f(r)
    del rows
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;
Group Rows &lt;/h4&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;def groupRows(inputFeatureClass, where, &lt;b&gt;keyFunction&lt;/b&gt;, &lt;b&gt;valueFunction&lt;/b&gt;):
    from collections import defaultdict

    groupings = defaultdict(list)
    rows = arcpy.SearchCursor(inputFeatureClass, where)
    for r in rows:
        groupings[&lt;b&gt;keyFunction(r)&lt;/b&gt;].append(&lt;b&gt;valueFunction(r)&lt;/b&gt;)
    del rows
    return groupings
&lt;/code&gt;&lt;/pre&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;With 10.1+, you can utilize the arcpy.da library for significant performance increase:

def groupRows2(inputFeatureClass, fields, where, &lt;b&gt;keyFunction&lt;/b&gt;, &lt;b&gt;valueFunction&lt;/b&gt;):
    from collections import defaultdict
    groupings = defaultdict(list)
    with arcpy.da.SearchCursor(inputFeautreClass, fields, where) as cursor:
        for r in cursor:
            groupings[&lt;b&gt;keyFunction(r)&lt;/b&gt;].append(&lt;b&gt;valueFunction(r)&lt;/b&gt;)
    return groupings
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
By convention, groupRows will call keyFunction(row) and valueFunction(row) supplying the current arcpy row object.  The result will be a dictionary mapping keyFunction outputs to &lt;b&gt;lists&lt;/b&gt; of valueFunction outputs:&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;if __name__ == &#39;__main__&#39;:
    featureClass = arcpy.GetParameterAsText(0) or r&#39;C:\python_working_directory\schools.gdb\school_points&#39;
    schoolIdField = arcpy.GetParameterAsText(1) or r&#39;schoolId&#39;
    whereClause = None
    &lt;b&gt;keyFunction = lambda r: r.getValue(stateSchoolId[:2])&lt;/b&gt;
    &lt;b&gt;valueFunction = lambda r: r.getValue(stateSchoolId)&lt;/b&gt;

    groupsByDistrict = groupRows(features, None, keyFunction, valueFunction) 

&amp;gt;&amp;gt;&amp;gt; {&#39;TX&#39;: [&#39;TX201&#39;, &#39;TX202&#39;, &#39;TX203&#39;]}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;
&amp;nbsp;&lt;/h4&gt;
&lt;h4&gt;
Translate Append&lt;/h4&gt;
Do you need to append to an existing feature class?&amp;nbsp; Basically the same data, but conflicting attribute tables?&amp;nbsp; Here&#39;s a functional solution:&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;def translateAppend(targetFeatureClass, appendFeatureClass, where, fieldTranslations, outputFeatureClass):
    import types
    
    arcpy.CopyFeatures_management(targetFeatureClass, outputFeatureClass)
    insertCursor = arcpy.InsertCursor(outputFeatureClass)
    appendRows = arcpy.SearchCursor(appendFeatureClass, where)
    
    for r in appendRows:
        newRow = insertCursor.newRow() 
        for k,v in fieldTranslations.items():
            value = None
            if isinstance(v, types.FunctionType):
                value = v(r)
            else:
                value = r.getValue(v)
            if value:
                newRow.setValue(k, value)
        insertCursor.insertRow(newRow)
    
    del newRow
    del appendRows
    del insertCursor
&lt;/code&gt;&lt;/pre&gt;
You supply the function with a dictionary whose keys are the fields your existing feature class.  The values of the dict are functions which output the value for that field based on your new append data. The values can be lambda expressions, standard function (def statement).  By convention, if you use a string value then that field from the append feature class is used:
&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;def createGradeList(row):
    low_grade = row.getValue(&#39;low_grade&#39;)
    high_grade = row.getValue(&#39;high_grade&#39;)
    return range(low_grade, high_grade, 1)
   
#Map fields in input feature class to strings or functions
translations = {}

#Straight field-to-field name copy
translations[&#39;Shape&#39;] = &#39;Shape&#39;

#generate value using lambda expression
translations[&#39;state&#39;] = lambda row: row.getValue(stateSchoolId)[:2] 

#lookup value in dictionary
translations[&#39;StateAbbrev&#39;] = lambda row: state_abbrevs_index[row.getValue(&#39;FIPS&#39;)]

# generate value using function
translations[&#39;GradesCommaDelimited&#39;] = createGradeList

translateAppend(feature_class, new_features, &quot;1=1&quot;, translations, output_feature_class)
&lt;/code&gt;&lt;/pre&gt;
</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/4749088683157832497/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2013/01/funky-arcpy-cursors-functional.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/4749088683157832497'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/4749088683157832497'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2013/01/funky-arcpy-cursors-functional.html' title='Funky Arcpy Cursors - functional programming examples'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-1686575104063357928</id><published>2012-09-23T20:58:00.001-07:00</published><updated>2012-09-23T20:59:08.563-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Imaging"/><category scheme="http://www.blogger.com/atom/ns#" term="PIL"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><title type='text'>Get Unique Pixel Values By Channel (RGBA) using Python</title><content type='html'>The following code sample shows how to extract a list of unique pixel values for each channel in an image using the &lt;a href=&quot;http://www.pythonware.com/library/pil/handbook/image.htm&quot;&gt;Python Imaging Library (PIL)&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;def getUniquePixelValuesByChannel(imagePath, hexBase=True):
    &#39;&#39;&#39;
    returns lists of unique pixel value for each channel in image(r,g,b,a)
    &#39;&#39;&#39;
    image = Image.open(imagePath)
    histogram = image.histogram()
    channels = [histogram[p : p + 256] for p in range(0, len(histogram), 256)]
    unique_pixels = []
    for c in channels:
        unique_pixels.append([hexBase and hex(p) or p for p in range(len(c)) if c[p] &amp;gt; 0])
    return unique_pixels
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
This function uses the image.histogram() method to get a list of the frequencies of each pixel value across each color channel (red, green, blue, alpha).&amp;nbsp; For a 4-band image, the resulting list is 1024 values long (256 for each channel) with the index of each item representing the color value in that channel.&amp;nbsp;&amp;nbsp; &lt;br /&gt;
&lt;br /&gt;
Below is an example of the function in-action returning the unique values for each channel in either hex or 256:&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;if __name__ == &#39;__main__&#39;:

    imagePath = r&#39;lookAtMyPixelValuesInHex.png&#39;

    pixelValuesInHex = getUniquePixelValuesByChannel(imagePath, True)
    print &#39;hex result:&#39;, pixelValuesInHex
    &#39;&#39;&#39;
    hex result: [
        [&#39;0x0&#39;, &#39;0x7&#39;, &#39;0xf&#39;, &#39;0x17&#39;, &#39;0x1f&#39;, &#39;0x27&#39;, &#39;0x37&#39;, &#39;0xfd&#39;], 
        [&#39;0x7&#39;, &#39;0xf&#39;, &#39;0x17&#39;, &#39;0x1f&#39;, &#39;0x27&#39;, &#39;0x2f&#39;, &#39;0xfd&#39;], 
        [&#39;0x7&#39;, &#39;0xf&#39;, &#39;0x17&#39;, &#39;0x1f&#39;, &#39;0xfd&#39;], 
        [&#39;0x7&#39;, &#39;0xf&#39;, &#39;0x17&#39;, &#39;0x1f&#39;, &#39;0x27&#39;, &#39;0x2f&#39;, &#39;0x37&#39;, &#39;0xfd&#39;]
    ]

    &#39;&#39;&#39;

    pixelValuesIn256 = getUniquePixelValuesByChannel(imagePath, False)
    print &#39;256 color result:&#39;, pixelValuesIn256
    &#39;&#39;&#39;
    256 color result: [
        [0, 7, 15, 23, 31, 39, 55, 253], 
        [7, 15, 23, 31, 39, 47, 253], 
        [7, 15, 23, 31, 253], 
        [7, 15, 23, 31, 39, 47, 55, 253]
    ]
    &#39;&#39;&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/1686575104063357928/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2012/09/get-unique-pixel-values-by-channel-rgba.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/1686575104063357928'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/1686575104063357928'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2012/09/get-unique-pixel-values-by-channel-rgba.html' title='Get Unique Pixel Values By Channel (RGBA) using Python'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-6298356002386192659</id><published>2012-09-18T22:26:00.000-07:00</published><updated>2012-09-23T20:59:35.077-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="fuzzy"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><title type='text'>Normalize to USPS Street Abbreviations using Python</title><content type='html'>Here&#39;s a function i recently wrote for converting street suffixes using common street suffixes mapped to the usps standard.&amp;nbsp; You can &lt;a href=&quot;https://www.usps.com/send/official-abbreviations.htm&quot; target=&quot;_blank&quot;&gt;find the list on the usps site&lt;/a&gt;. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Came in handy for some fuzzy matching of address strings...

&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;def normalizeStreetSuffixes(inputValue):
        &#39;&#39;&#39;
        Use common abbreviations -&amp;gt; USPS standardized abbreviation to replace common street suffixes

        Obtains list from https://www.usps.com/send/official-abbreviations.htm
        &#39;&#39;&#39;
        usps_street_abbreviations = {&#39;trpk&#39;: &#39;tpke&#39;, &#39;forges&#39;: &#39;frgs&#39;, &#39;bypas&#39;: &#39;byp&#39;, &#39;mnr&#39;: &#39;mnr&#39;, &#39;viaduct&#39;: &#39;via&#39;, &#39;mnt&#39;: &#39;mt&#39;,
         &#39;lndng&#39;: &#39;lndg&#39;, &#39;vill&#39;: &#39;vlg&#39;, &#39;aly&#39;: &#39;aly&#39;, &#39;mill&#39;: &#39;ml&#39;, &#39;pts&#39;: &#39;pts&#39;, &#39;centers&#39;: &#39;ctrs&#39;, &#39;row&#39;: &#39;row&#39;, &#39;cnter&#39;: &#39;ctr&#39;,
          &#39;hrbor&#39;: &#39;hbr&#39;, &#39;tr&#39;: &#39;trl&#39;, &#39;lndg&#39;: &#39;lndg&#39;, &#39;passage&#39;: &#39;psge&#39;, &#39;walks&#39;: &#39;walk&#39;, &#39;frks&#39;: &#39;frks&#39;, &#39;crest&#39;: &#39;crst&#39;, &#39;meadows&#39;: &#39;mdws&#39;,
           &#39;freewy&#39;: &#39;fwy&#39;, &#39;garden&#39;: &#39;gdn&#39;, &#39;bluffs&#39;: &#39;blfs&#39;, &#39;vlg&#39;: &#39;vlg&#39;, &#39;vly&#39;: &#39;vly&#39;, &#39;fall&#39;: &#39;fall&#39;, &#39;trk&#39;: &#39;trak&#39;, &#39;squares&#39;: &#39;sqs&#39;,
            &#39;trl&#39;: &#39;trl&#39;, &#39;harbor&#39;: &#39;hbr&#39;, &#39;frry&#39;: &#39;fry&#39;, &#39;div&#39;: &#39;dv&#39;, &#39;straven&#39;: &#39;stra&#39;, &#39;cmp&#39;: &#39;cp&#39;, &#39;grdns&#39;: &#39;gdns&#39;, &#39;villg&#39;: &#39;vlg&#39;,
             &#39;meadow&#39;: &#39;mdw&#39;, &#39;trails&#39;: &#39;trl&#39;, &#39;streets&#39;: &#39;sts&#39;, &#39;prairie&#39;: &#39;pr&#39;, &#39;hts&#39;: &#39;hts&#39;, &#39;crescent&#39;: &#39;cres&#39;, &#39;pass&#39;: &#39;pass&#39;,
              &#39;ter&#39;: &#39;ter&#39;, &#39;port&#39;: &#39;prt&#39;, &#39;bluf&#39;: &#39;blf&#39;, &#39;avnue&#39;: &#39;ave&#39;, &#39;lights&#39;: &#39;lgts&#39;, &#39;rpds&#39;: &#39;rpds&#39;, &#39;harbors&#39;: &#39;hbrs&#39;,
               &#39;mews&#39;: &#39;mews&#39;, &#39;lodg&#39;: &#39;ldg&#39;, &#39;plz&#39;: &#39;plz&#39;, &#39;tracks&#39;: &#39;trak&#39;, &#39;path&#39;: &#39;path&#39;, &#39;pkway&#39;: &#39;pkwy&#39;, &#39;gln&#39;: &#39;gln&#39;,
                &#39;bot&#39;: &#39;btm&#39;, &#39;drv&#39;: &#39;dr&#39;, &#39;rdg&#39;: &#39;rdg&#39;, &#39;fwy&#39;: &#39;fwy&#39;, &#39;hbr&#39;: &#39;hbr&#39;, &#39;via&#39;: &#39;via&#39;, &#39;divide&#39;: &#39;dv&#39;, &#39;inlt&#39;: &#39;inlt&#39;,
                 &#39;fords&#39;: &#39;frds&#39;, &#39;avenu&#39;: &#39;ave&#39;, &#39;vis&#39;: &#39;vis&#39;, &#39;brk&#39;: &#39;brk&#39;, &#39;rivr&#39;: &#39;riv&#39;, &#39;oval&#39;: &#39;oval&#39;, &#39;gateway&#39;: &#39;gtwy&#39;,
                  &#39;stream&#39;: &#39;strm&#39;, &#39;bayoo&#39;: &#39;byu&#39;, &#39;msn&#39;: &#39;msn&#39;, &#39;knoll&#39;: &#39;knl&#39;, &#39;expressway&#39;: &#39;expy&#39;, &#39;sprng&#39;: &#39;spg&#39;,
                   &#39;flat&#39;: &#39;flt&#39;, &#39;holw&#39;: &#39;holw&#39;, &#39;grden&#39;: &#39;gdn&#39;, &#39;trail&#39;: &#39;trl&#39;, &#39;jctns&#39;: &#39;jcts&#39;, &#39;rdgs&#39;: &#39;rdgs&#39;,
                    &#39;tunnel&#39;: &#39;tunl&#39;, &#39;ml&#39;: &#39;ml&#39;, &#39;fls&#39;: &#39;fls&#39;, &#39;flt&#39;: &#39;flt&#39;, &#39;lks&#39;: &#39;lks&#39;, &#39;mt&#39;: &#39;mt&#39;, &#39;groves&#39;: &#39;grvs&#39;,
                     &#39;vally&#39;: &#39;vly&#39;, &#39;ferry&#39;: &#39;fry&#39;, &#39;parkway&#39;: &#39;pkwy&#39;, &#39;radiel&#39;: &#39;radl&#39;, &#39;strvnue&#39;: &#39;stra&#39;, &#39;fld&#39;: &#39;fld&#39;,
                      &#39;overpass&#39;: &#39;opas&#39;, &#39;plaza&#39;: &#39;plz&#39;, &#39;estate&#39;: &#39;est&#39;, &#39;mntn&#39;: &#39;mtn&#39;, &#39;lock&#39;: &#39;lck&#39;, &#39;orchrd&#39;: &#39;orch&#39;,
                       &#39;strvn&#39;: &#39;stra&#39;, &#39;locks&#39;: &#39;lcks&#39;, &#39;bend&#39;: &#39;bnd&#39;, &#39;kys&#39;: &#39;kys&#39;, &#39;junctions&#39;: &#39;jcts&#39;, &#39;mountin&#39;: &#39;mtn&#39;,
                        &#39;burgs&#39;: &#39;bgs&#39;, &#39;pine&#39;: &#39;pne&#39;, &#39;ldge&#39;: &#39;ldg&#39;, &#39;causway&#39;: &#39;cswy&#39;, &#39;spg&#39;: &#39;spg&#39;, &#39;beach&#39;: &#39;bch&#39;, &#39;ft&#39;: &#39;ft&#39;,
                         &#39;crse&#39;: &#39;crse&#39;, &#39;motorway&#39;: &#39;mtwy&#39;, &#39;bluff&#39;: &#39;blf&#39;, &#39;court&#39;: &#39;ct&#39;, &#39;grov&#39;: &#39;grv&#39;, &#39;sprngs&#39;: &#39;spgs&#39;,
                          &#39;ovl&#39;: &#39;oval&#39;, &#39;villag&#39;: &#39;vlg&#39;, &#39;vdct&#39;: &#39;via&#39;, &#39;neck&#39;: &#39;nck&#39;, &#39;orchard&#39;: &#39;orch&#39;, &#39;light&#39;: &#39;lgt&#39;,
                           &#39;sq&#39;: &#39;sq&#39;, &#39;pkwy&#39;: &#39;pkwy&#39;, &#39;shore&#39;: &#39;shr&#39;, &#39;green&#39;: &#39;grn&#39;, &#39;strm&#39;: &#39;strm&#39;, &#39;islnd&#39;: &#39;is&#39;,
                            &#39;turnpike&#39;: &#39;tpke&#39;, &#39;stra&#39;: &#39;stra&#39;, &#39;mission&#39;: &#39;msn&#39;, &#39;spngs&#39;: &#39;spgs&#39;, &#39;course&#39;: &#39;crse&#39;,
                             &#39;trafficway&#39;: &#39;trfy&#39;, &#39;terrace&#39;: &#39;ter&#39;, &#39;hway&#39;: &#39;hwy&#39;, &#39;avenue&#39;: &#39;ave&#39;, &#39;glen&#39;: &#39;gln&#39;,
                              &#39;boul&#39;: &#39;blvd&#39;, &#39;inlet&#39;: &#39;inlt&#39;, &#39;la&#39;: &#39;ln&#39;, &#39;ln&#39;: &#39;ln&#39;, &#39;frst&#39;: &#39;frst&#39;, &#39;clf&#39;: &#39;clf&#39;,
                               &#39;cres&#39;: &#39;cres&#39;, &#39;brook&#39;: &#39;brk&#39;, &#39;lk&#39;: &#39;lk&#39;, &#39;byp&#39;: &#39;byp&#39;, &#39;shoar&#39;: &#39;shr&#39;, &#39;bypass&#39;: &#39;byp&#39;,
                                &#39;mtin&#39;: &#39;mtn&#39;, &#39;ally&#39;: &#39;aly&#39;, &#39;forest&#39;: &#39;frst&#39;, &#39;junction&#39;: &#39;jct&#39;, &#39;views&#39;: &#39;vws&#39;, &#39;wells&#39;: &#39;wls&#39;, &#39;cen&#39;: &#39;ctr&#39;,
                                 &#39;exts&#39;: &#39;exts&#39;, &#39;crt&#39;: &#39;ct&#39;, &#39;corners&#39;: &#39;cors&#39;, &#39;trak&#39;: &#39;trak&#39;, &#39;frway&#39;: &#39;fwy&#39;, &#39;prarie&#39;: &#39;pr&#39;, &#39;crossing&#39;: &#39;xing&#39;,
                                  &#39;extn&#39;: &#39;ext&#39;, &#39;cliffs&#39;: &#39;clfs&#39;, &#39;manors&#39;: &#39;mnrs&#39;, &#39;ports&#39;: &#39;prts&#39;, &#39;gatewy&#39;: &#39;gtwy&#39;, &#39;square&#39;: &#39;sq&#39;, &#39;hls&#39;: &#39;hls&#39;,
                                   &#39;harb&#39;: &#39;hbr&#39;, &#39;loops&#39;: &#39;loop&#39;, &#39;mdw&#39;: &#39;mdw&#39;, &#39;smt&#39;: &#39;smt&#39;, &#39;rd&#39;: &#39;rd&#39;, &#39;hill&#39;: &#39;hl&#39;, &#39;blf&#39;: &#39;blf&#39;,
                                    &#39;highway&#39;: &#39;hwy&#39;, &#39;walk&#39;: &#39;walk&#39;, &#39;clfs&#39;: &#39;clfs&#39;, &#39;brooks&#39;: &#39;brks&#39;, &#39;brnch&#39;: &#39;br&#39;, &#39;aven&#39;: &#39;ave&#39;,
                                     &#39;shores&#39;: &#39;shrs&#39;, &#39;iss&#39;: &#39;iss&#39;, &#39;route&#39;: &#39;rte&#39;, &#39;wls&#39;: &#39;wls&#39;, &#39;place&#39;: &#39;pl&#39;, &#39;sumit&#39;: &#39;smt&#39;, &#39;pines&#39;: &#39;pnes&#39;,
                                      &#39;trks&#39;: &#39;trak&#39;, &#39;shoal&#39;: &#39;shl&#39;, &#39;strt&#39;: &#39;st&#39;, &#39;frwy&#39;: &#39;fwy&#39;, &#39;heights&#39;: &#39;hts&#39;, &#39;ranches&#39;: &#39;rnch&#39;,
                                       &#39;boulevard&#39;: &#39;blvd&#39;, &#39;extnsn&#39;: &#39;ext&#39;, &#39;mdws&#39;: &#39;mdws&#39;, &#39;hollows&#39;: &#39;holw&#39;, &#39;vsta&#39;: &#39;vis&#39;, &#39;plains&#39;: &#39;plns&#39;,
                                        &#39;station&#39;: &#39;sta&#39;, &#39;circl&#39;: &#39;cir&#39;, &#39;mntns&#39;: &#39;mtns&#39;, &#39;prts&#39;: &#39;prts&#39;, &#39;shls&#39;: &#39;shls&#39;, &#39;villages&#39;: &#39;vlgs&#39;,
                                         &#39;park&#39;: &#39;park&#39;, &#39;nck&#39;: &#39;nck&#39;, &#39;rst&#39;: &#39;rst&#39;, &#39;haven&#39;: &#39;hvn&#39;, &#39;turnpk&#39;: &#39;tpke&#39;, &#39;expy&#39;: &#39;expy&#39;, &#39;sta&#39;: &#39;sta&#39;,
                                          &#39;expr&#39;: &#39;expy&#39;, &#39;stn&#39;: &#39;sta&#39;, &#39;expw&#39;: &#39;expy&#39;, &#39;street&#39;: &#39;st&#39;, &#39;str&#39;: &#39;st&#39;, &#39;spurs&#39;: &#39;spur&#39;, &#39;crecent&#39;: &#39;cres&#39;,
                                           &#39;rad&#39;: &#39;radl&#39;, &#39;ranch&#39;: &#39;rnch&#39;, &#39;well&#39;: &#39;wl&#39;, &#39;shoals&#39;: &#39;shls&#39;, &#39;alley&#39;: &#39;aly&#39;, &#39;plza&#39;: &#39;plz&#39;, &#39;medows&#39;: &#39;mdws&#39;,
                                            &#39;allee&#39;: &#39;aly&#39;, &#39;knls&#39;: &#39;knls&#39;, &#39;ests&#39;: &#39;ests&#39;, &#39;st&#39;: &#39;st&#39;, &#39;anx&#39;: &#39;anx&#39;, &#39;havn&#39;: &#39;hvn&#39;, &#39;paths&#39;: &#39;path&#39;, &#39;bypa&#39;: &#39;byp&#39;,
                                             &#39;spgs&#39;: &#39;spgs&#39;, &#39;mills&#39;: &#39;mls&#39;, &#39;parks&#39;: &#39;park&#39;, &#39;byps&#39;: &#39;byp&#39;, &#39;flts&#39;: &#39;flts&#39;, &#39;tunnels&#39;: &#39;tunl&#39;, &#39;club&#39;: &#39;clb&#39;, &#39;sqrs&#39;: &#39;sqs&#39;,
                                              &#39;hllw&#39;: &#39;holw&#39;, &#39;manor&#39;: &#39;mnr&#39;, &#39;centre&#39;: &#39;ctr&#39;, &#39;track&#39;: &#39;trak&#39;, &#39;hgts&#39;: &#39;hts&#39;, &#39;rnch&#39;: &#39;rnch&#39;, &#39;crcle&#39;: &#39;cir&#39;, &#39;falls&#39;: &#39;fls&#39;,
                                               &#39;landing&#39;: &#39;lndg&#39;, &#39;plaines&#39;: &#39;plns&#39;, &#39;viadct&#39;: &#39;via&#39;, &#39;gdns&#39;: &#39;gdns&#39;, &#39;gtwy&#39;: &#39;gtwy&#39;, &#39;grove&#39;: &#39;grv&#39;, &#39;camp&#39;: &#39;cp&#39;, &#39;tpk&#39;: &#39;tpke&#39;,
                                                &#39;drive&#39;: &#39;dr&#39;, &#39;freeway&#39;: &#39;fwy&#39;, &#39;ext&#39;: &#39;ext&#39;, &#39;points&#39;: &#39;pts&#39;, &#39;exp&#39;: &#39;expy&#39;, &#39;ky&#39;: &#39;ky&#39;, &#39;courts&#39;: &#39;cts&#39;, &#39;pky&#39;: &#39;pkwy&#39;, &#39;corner&#39;: &#39;cor&#39;,
                                                 &#39;crssing&#39;: &#39;xing&#39;, &#39;mnrs&#39;: &#39;mnrs&#39;, &#39;unions&#39;: &#39;uns&#39;, &#39;cyn&#39;: &#39;cyn&#39;, &#39;lodge&#39;: &#39;ldg&#39;, &#39;trfy&#39;: &#39;trfy&#39;, &#39;circle&#39;: &#39;cir&#39;, &#39;bridge&#39;: &#39;brg&#39;,
                                                  &#39;dl&#39;: &#39;dl&#39;, &#39;dm&#39;: &#39;dm&#39;, &#39;express&#39;: &#39;expy&#39;, &#39;tunls&#39;: &#39;tunl&#39;, &#39;dv&#39;: &#39;dv&#39;, &#39;dr&#39;: &#39;dr&#39;, &#39;shr&#39;: &#39;shr&#39;, &#39;knolls&#39;: &#39;knls&#39;, &#39;greens&#39;: &#39;grns&#39;,
                                                   &#39;tunel&#39;: &#39;tunl&#39;, &#39;fields&#39;: &#39;flds&#39;, &#39;common&#39;: &#39;cmn&#39;, &#39;orch&#39;: &#39;orch&#39;, &#39;crk&#39;: &#39;crk&#39;, &#39;river&#39;: &#39;riv&#39;, &#39;shl&#39;: &#39;shl&#39;, &#39;view&#39;: &#39;vw&#39;,
                                                    &#39;crsent&#39;: &#39;cres&#39;, &#39;rnchs&#39;: &#39;rnch&#39;, &#39;crscnt&#39;: &#39;cres&#39;, &#39;arc&#39;: &#39;arc&#39;, &#39;btm&#39;: &#39;btm&#39;, &#39;blvd&#39;: &#39;blvd&#39;, &#39;ways&#39;: &#39;ways&#39;, &#39;radl&#39;: &#39;radl&#39;,
                                                     &#39;rdge&#39;: &#39;rdg&#39;, &#39;causeway&#39;: &#39;cswy&#39;, &#39;parkwy&#39;: &#39;pkwy&#39;, &#39;juncton&#39;: &#39;jct&#39;, &#39;statn&#39;: &#39;sta&#39;, &#39;gardn&#39;: &#39;gdn&#39;, &#39;mntain&#39;: &#39;mtn&#39;,
                                                      &#39;crssng&#39;: &#39;xing&#39;, &#39;rapid&#39;: &#39;rpd&#39;, &#39;key&#39;: &#39;ky&#39;, &#39;plns&#39;: &#39;plns&#39;, &#39;wy&#39;: &#39;way&#39;, &#39;cor&#39;: &#39;cor&#39;, &#39;ramp&#39;: &#39;ramp&#39;, &#39;throughway&#39;: &#39;trwy&#39;,
                                                       &#39;estates&#39;: &#39;ests&#39;, &#39;ck&#39;: &#39;crk&#39;, &#39;loaf&#39;: &#39;lf&#39;, &#39;hvn&#39;: &#39;hvn&#39;, &#39;wall&#39;: &#39;wall&#39;, &#39;hollow&#39;: &#39;holw&#39;, &#39;canyon&#39;: &#39;cyn&#39;, &#39;clb&#39;: &#39;clb&#39;,
                                                        &#39;cswy&#39;: &#39;cswy&#39;, &#39;village&#39;: &#39;vlg&#39;, &#39;cr&#39;: &#39;crk&#39;, &#39;trce&#39;: &#39;trce&#39;, &#39;cp&#39;: &#39;cp&#39;, &#39;cv&#39;: &#39;cv&#39;, &#39;ct&#39;: &#39;cts&#39;, &#39;pr&#39;: &#39;pr&#39;, &#39;frg&#39;: &#39;frg&#39;,
                                                         &#39;jction&#39;: &#39;jct&#39;, &#39;pt&#39;: &#39;pt&#39;, &#39;mssn&#39;: &#39;msn&#39;, &#39;frk&#39;: &#39;frk&#39;, &#39;brdge&#39;: &#39;brg&#39;, &#39;cent&#39;: &#39;ctr&#39;, &#39;spur&#39;: &#39;spur&#39;, &#39;frt&#39;: &#39;ft&#39;, &#39;pk&#39;: &#39;park&#39;,
                                                          &#39;fry&#39;: &#39;fry&#39;, &#39;pl&#39;: &#39;pl&#39;, &#39;lanes&#39;: &#39;ln&#39;, &#39;gtway&#39;: &#39;gtwy&#39;, &#39;prk&#39;: &#39;park&#39;, &#39;vws&#39;: &#39;vws&#39;, &#39;stravenue&#39;: &#39;stra&#39;, &#39;lgt&#39;: &#39;lgt&#39;,
                                                           &#39;hiway&#39;: &#39;hwy&#39;, &#39;ctr&#39;: &#39;ctr&#39;, &#39;prt&#39;: &#39;prt&#39;, &#39;ville&#39;: &#39;vl&#39;, &#39;plain&#39;: &#39;pln&#39;, &#39;mount&#39;: &#39;mt&#39;, &#39;mls&#39;: &#39;mls&#39;, &#39;loop&#39;: &#39;loop&#39;,
                                                            &#39;riv&#39;: &#39;riv&#39;, &#39;centr&#39;: &#39;ctr&#39;, &#39;is&#39;: &#39;is&#39;, &#39;prr&#39;: &#39;pr&#39;, &#39;vl&#39;: &#39;vl&#39;, &#39;avn&#39;: &#39;ave&#39;, &#39;vw&#39;: &#39;vw&#39;, &#39;ave&#39;: &#39;ave&#39;, &#39;spng&#39;: &#39;spg&#39;,
                                                             &#39;hiwy&#39;: &#39;hwy&#39;, &#39;dam&#39;: &#39;dm&#39;, &#39;isle&#39;: &#39;isle&#39;, &#39;crcl&#39;: &#39;cir&#39;, &#39;sqre&#39;: &#39;sq&#39;, &#39;jct&#39;: &#39;jct&#39;, &#39;jctn&#39;: &#39;jct&#39;, &#39;mountain&#39;: &#39;mtn&#39;,
                                                              &#39;keys&#39;: &#39;kys&#39;, &#39;parkways&#39;: &#39;pkwy&#39;, &#39;drives&#39;: &#39;drs&#39;, &#39;tunl&#39;: &#39;tunl&#39;, &#39;jcts&#39;: &#39;jcts&#39;, &#39;knl&#39;: &#39;knl&#39;, &#39;center&#39;: &#39;ctr&#39;,
                                                               &#39;driv&#39;: &#39;dr&#39;, &#39;tpke&#39;: &#39;tpke&#39;, &#39;sumitt&#39;: &#39;smt&#39;, &#39;canyn&#39;: &#39;cyn&#39;, &#39;ldg&#39;: &#39;ldg&#39;, &#39;harbr&#39;: &#39;hbr&#39;, &#39;rest&#39;: &#39;rst&#39;, &#39;shoars&#39;: &#39;shrs&#39;,
                                                                &#39;vist&#39;: &#39;vis&#39;, &#39;gdn&#39;: &#39;gdn&#39;, &#39;islnds&#39;: &#39;iss&#39;, &#39;hills&#39;: &#39;hls&#39;, &#39;cresent&#39;: &#39;cres&#39;, &#39;point&#39;: &#39;pt&#39;, &#39;lake&#39;: &#39;lk&#39;, &#39;vlly&#39;: &#39;vly&#39;,
                                                                 &#39;strav&#39;: &#39;stra&#39;, &#39;crossroad&#39;: &#39;xrd&#39;, &#39;bnd&#39;: &#39;bnd&#39;, &#39;strave&#39;: &#39;stra&#39;, &#39;stravn&#39;: &#39;stra&#39;, &#39;knol&#39;: &#39;knl&#39;, &#39;vlgs&#39;: &#39;vlgs&#39;,
                                                                  &#39;forge&#39;: &#39;frg&#39;, &#39;cntr&#39;: &#39;ctr&#39;, &#39;cape&#39;: &#39;cpe&#39;, &#39;height&#39;: &#39;hts&#39;, &#39;lck&#39;: &#39;lck&#39;, &#39;highwy&#39;: &#39;hwy&#39;, &#39;trnpk&#39;: &#39;tpke&#39;, &#39;rpd&#39;: &#39;rpd&#39;,
                                                                   &#39;boulv&#39;: &#39;blvd&#39;, &#39;circles&#39;: &#39;cirs&#39;, &#39;valleys&#39;: &#39;vlys&#39;, &#39;vst&#39;: &#39;vis&#39;, &#39;creek&#39;: &#39;crk&#39;, &#39;mall&#39;: &#39;mall&#39;, &#39;spring&#39;: &#39;spg&#39;,
                                                                    &#39;brg&#39;: &#39;brg&#39;, &#39;holws&#39;: &#39;holw&#39;, &#39;lf&#39;: &#39;lf&#39;, &#39;est&#39;: &#39;est&#39;, &#39;xing&#39;: &#39;xing&#39;, &#39;trace&#39;: &#39;trce&#39;, &#39;bottom&#39;: &#39;btm&#39;,
                                                                     &#39;streme&#39;: &#39;strm&#39;, &#39;isles&#39;: &#39;isle&#39;, &#39;circ&#39;: &#39;cir&#39;, &#39;forks&#39;: &#39;frks&#39;, &#39;burg&#39;: &#39;bg&#39;, &#39;run&#39;: &#39;run&#39;, &#39;trls&#39;: &#39;trl&#39;,
                                                                      &#39;radial&#39;: &#39;radl&#39;, &#39;lakes&#39;: &#39;lks&#39;, &#39;rue&#39;: &#39;rue&#39;, &#39;vlys&#39;: &#39;vlys&#39;, &#39;br&#39;: &#39;br&#39;, &#39;cors&#39;: &#39;cors&#39;, &#39;pln&#39;: &#39;pln&#39;,
                                                                       &#39;pike&#39;: &#39;pike&#39;, &#39;extension&#39;: &#39;ext&#39;, &#39;island&#39;: &#39;is&#39;, &#39;frd&#39;: &#39;frd&#39;, &#39;lcks&#39;: &#39;lcks&#39;, &#39;terr&#39;: &#39;ter&#39;,
                                                                        &#39;union&#39;: &#39;un&#39;, &#39;extensions&#39;: &#39;exts&#39;, &#39;pkwys&#39;: &#39;pkwy&#39;, &#39;islands&#39;: &#39;iss&#39;, &#39;road&#39;: &#39;rd&#39;, &#39;shrs&#39;: &#39;shrs&#39;,
                                                                         &#39;roads&#39;: &#39;rds&#39;, &#39;glens&#39;: &#39;glns&#39;, &#39;springs&#39;: &#39;spgs&#39;, &#39;missn&#39;: &#39;msn&#39;, &#39;ridge&#39;: &#39;rdg&#39;, &#39;arcade&#39;: &#39;arc&#39;,
                                                                          &#39;bayou&#39;: &#39;byu&#39;, &#39;crsnt&#39;: &#39;cres&#39;, &#39;junctn&#39;: &#39;jct&#39;, &#39;way&#39;: &#39;way&#39;, &#39;valley&#39;: &#39;vly&#39;, &#39;fork&#39;: &#39;frk&#39;,
                                                                           &#39;mountains&#39;: &#39;mtns&#39;, &#39;bottm&#39;: &#39;btm&#39;, &#39;forg&#39;: &#39;frg&#39;, &#39;ht&#39;: &#39;hts&#39;, &#39;ford&#39;: &#39;frd&#39;, &#39;hl&#39;: &#39;hl&#39;,
                                                                            &#39;grdn&#39;: &#39;gdn&#39;, &#39;fort&#39;: &#39;ft&#39;, &#39;traces&#39;: &#39;trce&#39;, &#39;cnyn&#39;: &#39;cyn&#39;, &#39;cir&#39;: &#39;cir&#39;, &#39;un&#39;: &#39;un&#39;, &#39;mtn&#39;: &#39;mtn&#39;,
                                                                             &#39;flats&#39;: &#39;flts&#39;, &#39;anex&#39;: &#39;anx&#39;, &#39;gatway&#39;: &#39;gtwy&#39;, &#39;rapids&#39;: &#39;rpds&#39;, &#39;villiage&#39;: &#39;vlg&#39;, &#39;flds&#39;: &#39;flds&#39;,
                                                                              &#39;coves&#39;: &#39;cvs&#39;, &#39;rvr&#39;: &#39;riv&#39;, &#39;av&#39;: &#39;ave&#39;, &#39;pikes&#39;: &#39;pike&#39;, &#39;grv&#39;: &#39;grv&#39;, &#39;vista&#39;: &#39;vis&#39;, &#39;pnes&#39;: &#39;pnes&#39;,
                                                                               &#39;forests&#39;: &#39;frst&#39;, &#39;field&#39;: &#39;fld&#39;, &#39;branch&#39;: &#39;br&#39;, &#39;grn&#39;: &#39;grn&#39;, &#39;dale&#39;: &#39;dl&#39;, &#39;rds&#39;: &#39;rds&#39;, &#39;annex&#39;: &#39;anx&#39;,
                                                                                &#39;sqr&#39;: &#39;sq&#39;, &#39;cove&#39;: &#39;cv&#39;, &#39;squ&#39;: &#39;sq&#39;, &#39;skyway&#39;: &#39;skwy&#39;, &#39;ridges&#39;: &#39;rdgs&#39;, &#39;hwy&#39;: &#39;hwy&#39;, &#39;tunnl&#39;: &#39;tunl&#39;,
                                                                                 &#39;underpass&#39;: &#39;upas&#39;, &#39;cliff&#39;: &#39;clf&#39;, &#39;lane&#39;: &#39;ln&#39;, &#39;land&#39;: &#39;land&#39;, &#39;bch&#39;: &#39;bch&#39;, &#39;dvd&#39;: &#39;dv&#39;, &#39;curve&#39;: &#39;curv&#39;,
                                                                                  &#39;cpe&#39;: &#39;cpe&#39;, &#39;summit&#39;: &#39;smt&#39;, &#39;gardens&#39;: &#39;gdns&#39;}
        words = inputValue.split()
        for w in words:
            if w in usps_street_abbreviations.keys():
                inputValue = inputValue.replace(w, usps_street_abbreviations[w])
        return inputValue
&lt;/code&gt;&lt;/pre&gt;
</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/6298356002386192659/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2012/09/normalize-to-usps-street-abbreviations.html#comment-form' title='38 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/6298356002386192659'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/6298356002386192659'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2012/09/normalize-to-usps-street-abbreviations.html' title='Normalize to USPS Street Abbreviations using Python'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>38</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-5550261302315048752</id><published>2012-05-07T20:33:00.000-07:00</published><updated>2012-05-07T20:36:59.021-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AIR"/><category scheme="http://www.blogger.com/atom/ns#" term="ArcGIS Server"/><category scheme="http://www.blogger.com/atom/ns#" term="Django"/><category scheme="http://www.blogger.com/atom/ns#" term="ESRI Developer Summit"/><category scheme="http://www.blogger.com/atom/ns#" term="Flex"/><category scheme="http://www.blogger.com/atom/ns#" term="Presentations"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><category scheme="http://www.blogger.com/atom/ns#" term="SQLite"/><title type='text'>Online/Offline Mapping: ESRI Dev Summit 2012</title><content type='html'>I had a great time out in Palm Springs this year. &amp;nbsp;Saw some great presentations and will be definitely going back over the videos. &amp;nbsp;Looks like ESRI has released many of them in their re-vamped video site. &lt;br /&gt;
&lt;br /&gt;
I presented this year on converting an existing Flex application to AIR and running it offline:&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://video.esri.com/watch/1226/developing-online_slash_offline-mapping-applications-using-adobe-air-and-arcgis-api-for-flex&quot; target=&quot;_blank&quot;&gt;Here&#39;s a link to my presentation.&amp;nbsp;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;iframe frameborder=&quot;0&quot; height=&quot;405&quot; scrolling=&quot;no&quot; src=&quot;http://video.esri.com/iframe/1226/000000/width/720/0/00:00:00&quot; width=&quot;720&quot;&gt;&lt;/iframe&gt;</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/5550261302315048752/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2012/05/onlineoffline-mapping-esri-dev-summit.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/5550261302315048752'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/5550261302315048752'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2012/05/onlineoffline-mapping-esri-dev-summit.html' title='Online/Offline Mapping: ESRI Dev Summit 2012'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-8046316458585671629</id><published>2012-02-13T21:03:00.001-08:00</published><updated>2012-03-22T14:55:00.570-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Map Tiles"/><category scheme="http://www.blogger.com/atom/ns#" term="Offline Mapping"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><title type='text'>Online/Offline Mapping: Finding tile urls based on a geographic extent</title><content type='html'>This is my second post in a series on developing web mapping applications which also function offline.&amp;nbsp; My last post covered map tile url schemas for various basemap providers.&amp;nbsp; While ESRI, Open Street Map, Google, Bing, and Yahoo / Nokia structure their urls differently, they all use the same column (x),  row (y), and zoom level (z) values for a indicating tile position within a quad tree.&amp;nbsp; If you haven&#39;t skimmed over that post, or this sounds like Klingon, I recommend &lt;a href=&quot;http://bcdcspatial.blogspot.com/2012/01/onlineoffline-mapping-map-tiles-and.html&quot; target=&quot;_blank&quot;&gt;checking that post out&lt;/a&gt;.&amp;nbsp; &lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;font-family: inherit;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: small;&quot;&gt;The here&#39;s the next challenge:&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;font-size: small;&quot;&gt; take a geographic area of interest, and find urls for all the tiles which covering that area (at zoom levels of interest).&amp;nbsp; &lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;font-family: inherit;&quot;&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div style=&quot;font-family: inherit;&quot;&gt;My area of interest will be &lt;a href=&quot;http://gisweb.cityofno.com/ArcGIS/rest/services/&quot; target=&quot;_blank&quot;&gt;the fine City of New Orleans&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;    &#39;&#39;&#39;
    The City of New Orleans
    xmin: -90.283741
    ymin: 29.890626
    xmax: -89.912952
    ymax: 30.057766
    &#39;&#39;&#39;
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;u&gt;&lt;b&gt;Disclaimer:&lt;/b&gt;&lt;/u&gt;&amp;nbsp; Persisting map tiles locally breaks the terms  of service for many popular basemap providers like Google, Bing, and  ArcGIS Online.&amp;nbsp; I&#39;m not a lawyer, nor do I aspire to be one, but if you  start storing tiles offline without permission, then you do so &lt;b&gt;&lt;i&gt;at your own risk&lt;/i&gt;&lt;/b&gt;.&amp;nbsp; &lt;br /&gt;
&lt;br /&gt;
&lt;u&gt;&lt;b&gt;&lt;span style=&quot;font-size: small;&quot;&gt;Helper Methods for Finding Tiles&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;&lt;span style=&quot;font-size: small;&quot;&gt;There are a bunch of functions you will need at your disposal to accomplish this task. These functions can be found in &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/bb259689.aspx&quot; target=&quot;_blank&quot;&gt;Joe Schwartz&#39;s Awesome White Paper on the Bing Maps Tiling System&lt;/a&gt;.&amp;nbsp; This is one of the best explanations of map tile systems I&#39;ve found on the web.&amp;nbsp; Joe goes over the mercator projection, along with explanations of ground resolution, map scale, and pixel coordinates. Really really great stuff and I urge everybody to really dig into it.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;font-family: inherit;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;span style=&quot;font-size: small;&quot;&gt;At the end of the article, Joe provides sample code which contains the major functions you will need to dominate map tile systems.&amp;nbsp; While I&#39;ve grown to really like C# development, I almost always translate interesting code back into warm and friendly Python.&amp;nbsp;&amp;nbsp; Here&#39;s Joe&#39;s TileSystem class translated into Python with a couple extra methods for additional abstraction:&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;&#39;&#39;&#39;
Mostly direct port of awesome article by Joe Schwartz - http://msdn.microsoft.com/en-us/library/bb259689.aspx
&#39;&#39;&#39;
from __future__ import division
import math

class TileUtils(object):

    earth_radius = 6378137
    min_lat = -85.05112878
    max_lat = 85.05112878
    min_lng = -180
    max_lng = 180

    def clipValue(self, value, minValue, maxValue):
        &#39;&#39;&#39;
        Makes sure that value is within a specific range.
        If not, then the lower or upper bounds is returned
        &#39;&#39;&#39;
        return min(max(value, minValue), maxValue)

    def getMapDimensionsByZoomLevel(self, zoomLevel):
        &#39;&#39;&#39;
        Returns the width/height in pixels of the entire map
        based on the zoom level.
        &#39;&#39;&#39;
        return 256 &amp;lt;&amp;lt; zoomLevel

    def getGroundResolution(self, latitude, level):
        &#39;&#39;&#39;
        returns the ground resolution for based on latitude and zoom level.
        &#39;&#39;&#39;
        latitude = self.clipValue(latitude, self.min_lat, self.max_lat);
        mapSize = self.getMapDimensionsByZoomLevel(level)
        return math.cos(latitude * math.pi / 180) * 2 * math.pi * self.earth_radius / mapSize

    def getMapScale(self, latitude, level, dpi=96):
        &#39;&#39;&#39;
        returns the map scale on the dpi of the screen
        &#39;&#39;&#39;
        dpm = dpi / 0.0254 #convert to dots per meter
        return self.getGroundResolution(latitude, level) * dpm

    def convertLatLngToPixelXY(self, lat, lng, level):
        &#39;&#39;&#39;
        returns the x and y values of the pixel corresponding to a latitude and longitude.
        &#39;&#39;&#39;
        mapSize = self.getMapDimensionsByZoomLevel(level)
       
        lat = self.clipValue(lat, self.min_lat, self.max_lat)
        lng = self.clipValue(lng, self.min_lng, self.max_lng)

        x = (lng + 180) / 360
        sinlat = math.sin(lat * math.pi / 180)
        y = 0.5 - math.log((1 + sinlat) / (1 - sinlat)) / (4 * math.pi)

        pixelX = int(self.clipValue(x * mapSize + 0.5, 0, mapSize - 1))
        pixelY = int(self.clipValue(y * mapSize + 0.5, 0, mapSize - 1))
        return (pixelX, pixelY)

    def convertPixelXYToLngLat(self, pixelX, pixelY, level):
        &#39;&#39;&#39;
        converts a pixel x, y to a latitude and longitude.
        &#39;&#39;&#39;
        mapSize = self.getMapDimensionsByZoomLevel(level)
        x = (self.clipValue(pixelX, 0, mapSize - 1) / mapSize) - 0.5
        y = 0.5 - (self.clipValue(pixelY, 0, mapSize - 1) / mapSize)

        lat = 90 - 360 * math.atan(math.exp(-y * 2 * math.pi)) / math.pi
        lng = 360 * x

        return (lng, lat)

    def convertPixelXYToTileXY(self, pixelX, pixelY):
        &#39;&#39;&#39;
        Converts pixel XY coordinates into tile XY coordinates of the tile containing
        &#39;&#39;&#39;
        return(int(pixelX / 256), int(pixelY / 256))

    def convertTileXYToPixelXY(self, tileX, tileY):
        &#39;&#39;&#39;
        Converts tile XY coordinates into pixel XY coordinates of the upper-left pixel
        &#39;&#39;&#39;
        return(tileX * 256, tileY * 256)

    def tileXYZToQuadKey(self, x, y, z):
        &#39;&#39;&#39;
        Computes quadKey value based on tile x, y and z values.
        &#39;&#39;&#39;
        quadKey = &#39;&#39;
        for i in range(z, 0, -1):
            digit = 0
            mask = 1 &amp;lt;&amp;lt; (i - 1)
            if(x &amp;amp; mask) != 0:
                digit += 1
            if(y &amp;amp; mask) != 0:
                digit += 2
            quadKey += str(digit)
        return quadKey

    def quadKeyToTileXYZ(self, quadKey):
        &#39;&#39;&#39;
        Computes tile x, y and z values based on quadKey.
        &#39;&#39;&#39;
        tileX = 0
        tileY = 0
        tileZ = len(quadKey)

        for i in range(tileZ, 0, -1):
            mask = 1 &amp;lt;&amp;lt; (i - 1)
            value = quadKey[tileZ - i]

            if value == &#39;0&#39;:
                continue

            elif value == &#39;1&#39;:
                tileX |= mask

            elif value == &#39;2&#39;:
                tileY |= mask

            elif value == &#39;3&#39;:
                tileX |= mask
                tileY |= mask

            else:
                raise Exception(&#39;Invalid QuadKey&#39;)

        return (tileX, tileY, tileZ)

    def convertLngLatToTileXY(self, lng, lat, level):
        pixelX, pixelY = self.convertLatLngToPixelXY(lat, lng, level)
        return self.convertPixelXYToTileXY(pixelX, pixelY)

    def getTileOrigin(self, tileX, tileY, level):
        &#39;&#39;&#39;
        Returns the upper-left hand corner lat/lng for a tile
        &#39;&#39;&#39;
        pixelX, pixelY = self.convertTileXYToPixelXY(tileX, tileY)
        lng, lat = self.convertPixelXYToLngLat(pixelX, pixelY, level)
        return (lat, lng)
&lt;/code&gt;&lt;/pre&gt;&lt;div style=&quot;font-family: inherit;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;span style=&quot;font-size: small;&quot;&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;font-family: inherit;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;span style=&quot;font-size: small;&quot;&gt;The function &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;convertLngLatToTileXY&lt;/span&gt; really sums up the class for our purposes. This function will help get the tile x/y values for the sides of the bounding box.&amp;nbsp; Using these functions, we can write an additional helper class to generate all the tile urls within our bounding box based off of a template tile url like the ones mentioned in my last post:&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;class TileFinder(object):

    def __init__(self, tileUtils, tileTemplate):
        &#39;&#39;&#39;
        Tile template is a template url which use {{x}}, {{y}}, {{z}} which will be replaced by
        their respective values.
        &#39;&#39;&#39;
        self.tileUtils = tileUtils
        self.tileTemplate = tileTemplate
    
    def getTileUrlsByLatLngExtent(self, xmin, ymin, xmax, ymax, level):
        &#39;&#39;&#39;
        Returns a list of tile urls by extent
        &#39;&#39;&#39;
        #Upper-Left Tile
        tileXMin, tileYMin = self.tileUtils.convertLngLatToTileXY(xmin, ymax, level)

        #Lower-Right Tile
        tileXMax, tileYMax = self.tileUtils.convertLngLatToTileXY(xmax, ymin, level)

        tileUrls = []

        for y in range(tileYMax, tileYMin - 1, -1):
            for x in range(tileXMin, tileXMax + 1, 1):
                tileUrls.append(self.createTileUrl(x, y, level)) 

        return tileUrls

    def createTileUrl(self, x, y, z):
        &#39;&#39;&#39;
        returns new tile url based on template
        &#39;&#39;&#39;
        return self.tileTemplate.replace(&#39;{{x}}&#39;, str(x)).replace(&#39;{{y}}&#39;, str(y)).replace(&#39;{{z}}&#39;, str(z))
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;&lt;span style=&quot;font-size: small;&quot;&gt;Check out the constructor here.&amp;nbsp; It has two required arguments, one of which is a tile template.&amp;nbsp; This tile template uses &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;{{x}}&lt;/span&gt;, &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;{{y}}&lt;/span&gt;, and &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;{{z}}&lt;/span&gt; values to indicate where substitutions should take place.&amp;nbsp; These substitutions happen in &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;createTileUrl&lt;/span&gt; which is called for each x,y pair generated in &lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;getTileUrlsByLatLngExtent.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;&lt;span style=&quot;font-size: small;&quot;&gt;So now we have our helper classes for generating a list of tile urls based on an extent.&amp;nbsp; The last value to complete the trifecta is the zoom level.&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;span style=&quot;font-size: small;&quot;&gt;I chose level 11 &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;span style=&quot;font-size: small;&quot;&gt;since my area of interest is fairly small (city level).&amp;nbsp; Here is a snippet for running the code:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;    &#39;&#39;&#39;
    The City of New Orleans
    xmin: -90.283741
    ymin: 29.890626
    xmax: -89.912952
    ymax: 30.057766
    &#39;&#39;&#39;

    import urllib2
    
    #constructing the template url for arcgis online
    template = &#39;http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{{z}}/{{y}}/{{x}}.png&#39;

    tileUtils = TileUtils()
    tileFinder = TileFinder(tileUtils, template)
    tile_urls = tileFinder.getTileUrlsByLatLngExtent(-90.283741, 29.890626, -89.912952, 30.057766, 11)

    for t in tile_urls: urllib2.urlopen(t).read()
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;&lt;span style=&quot;font-size: small;&quot;&gt;So that&#39;s about all there is to it.&amp;nbsp; Below you can see the six tile urls that were produced in the code snippet above.&amp;nbsp; A special thanks to Stephen Ansari (&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;username js-action-profile-name&quot;&gt;@sansari22&lt;/span&gt;)&lt;span style=&quot;font-size: large;&quot;&gt;&lt;span style=&quot;font-size: small;&quot;&gt; who really got me started on learning tile schemas and provided me sample code to get going.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/11/844/510.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/11/844/510.png&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/11/844/511.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/11/844/511.png&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/11/844/512.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/11/844/512.png&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/11/845/510.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/11/845/510.png&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/11/845/511.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/11/845/511.png&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/11/845/512.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/11/845/512.png&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/8046316458585671629/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2012/02/onlineoffline-mapping-finding-tile-urls.html#comment-form' title='10 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/8046316458585671629'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/8046316458585671629'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2012/02/onlineoffline-mapping-finding-tile-urls.html' title='Online/Offline Mapping: Finding tile urls based on a geographic extent'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-2847517160571748334</id><published>2012-01-29T14:36:00.001-08:00</published><updated>2012-02-29T17:39:41.488-08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ArcGIS Server"/><category scheme="http://www.blogger.com/atom/ns#" term="Bing"/><category scheme="http://www.blogger.com/atom/ns#" term="Google"/><category scheme="http://www.blogger.com/atom/ns#" term="Map Tiles"/><category scheme="http://www.blogger.com/atom/ns#" term="Nokia"/><category scheme="http://www.blogger.com/atom/ns#" term="Offline Mapping"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><category scheme="http://www.blogger.com/atom/ns#" term="Yahoo"/><title type='text'>Online/Offline Mapping: Map Tiles and their URL Schemas</title><content type='html'>I&#39;ve recently been developing online/offline mapping applications for use in places which simply don&#39;t have access to internet connections.&amp;nbsp; I&#39;m currently preparing a presentation discussing topics related to bring online application to offline users, and in doing so will be blogging on related topics.&amp;nbsp; The first part of the series will be dedicated to working with tile map services.&lt;br /&gt;
&lt;br /&gt;
One of the main challenges of bringing an online map to an offline environment is maintaining access to map tiles. From imagery to streets, tiled map services provide the necessary geographic context and semantic detail to visualize additional spatial layers.&amp;nbsp; While hard-drive space is still a major limiting factor in ability to store tiles, a significant number of tiles can be stored locally to allow use of an application for limited geographic area.&lt;br /&gt;
&lt;br /&gt;
&lt;u&gt;&lt;b&gt;Disclaimer:&lt;/b&gt;&lt;/u&gt;&amp;nbsp; Persisting map tiles locally breaks the terms of service for many popular basemap providers like Google, Bing, and ArcGIS Online.&amp;nbsp; I&#39;m not a lawyer, nor do I aspire to be one, but if you start storing tiles offline without permission, then you do so &lt;b&gt;&lt;i&gt;at your own risk&lt;/i&gt;&lt;/b&gt;.&amp;nbsp; With that said, let&#39;s look at how to get started in storing map tiles for offline use.&lt;br /&gt;
&lt;u&gt;&lt;br /&gt;
&lt;/u&gt;&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;&lt;u&gt;&lt;b&gt;Map Tiles and their URL Schemas&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
If you want to save down map tiles, you need to first know the address of each tile.&amp;nbsp; Map tile addresses take the form of urls (uniform address locators) which web maps access via HTTP GET requests.&amp;nbsp; Map tile service providers structure their map tile urls differently, but they all use column (x), row (y), and zoom level (z) values somewhere in the url.&amp;nbsp; Most providers have settled on using the &lt;a href=&quot;http://en.wikipedia.org/wiki/Mercator_projection&quot; target=&quot;_blank&quot;&gt;Web Mercator projection (sad but true)&lt;/a&gt;, and most have used the same origin for tiling and tile dimensions which means that the zoom level, row and column ids are the same across providers.&amp;nbsp; Dimensions are almost all 256 by 256 pixels and are in png or jpeg format.&lt;br /&gt;
&lt;br /&gt;
Below I have listed requests for a single tile covering the same geographic area (Pacific Ocean / California) for several basemap tile providers including: Open Street Map, ArcGIS Online, Google Maps, Bing Maps, Yahoo! Maps and Nokia.&amp;nbsp; Here are the details for the tile which is common to ALL providers and demonstrates how easy it can be to locate a tile for any of the providers below:&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;/*
Tile Details:
COLUMN (X): 2
ROW (Y): 6
ZOOM LEVEL (Z): 4
DIMENSIONS: 256 x 256
*/
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;u&gt;&lt;b&gt;&lt;span style=&quot;font-size: small;&quot;&gt;Open Street Map:&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: small;&quot;&gt;O.S.M. uses a simple directory structure for storing its map tiles which we will see repeated in many of the other service providers.&amp;nbsp; While you may think that &quot;Open&quot; means you can freely scrape as many tiles as you want...remember that OSM is hosted by volunteers with limited server resources.&amp;nbsp; If you want to scrape a large number of tiles, check out hosting your own OSM tile server.&amp;nbsp; I used &lt;a href=&quot;http://tiledrawer.com/&quot; target=&quot;_blank&quot;&gt;Michal Migurski&#39;s TileDrawer &lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;font-size: small;&quot;&gt;to quickly setup my own OSM EC2 instance.&amp;nbsp; Tile Drawer is great and doesn&#39;t even require you to SSH into the machine!&amp;nbsp; Just start it up and start scraping.&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;http://c.tile.openstreetmap.org/4/2/6.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://c.tile.openstreetmap.org/4/2/6.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;/*
Open Street Map Template:
http://{SERVER}/{ZOOM_LEVEL}/{COLUMN}/{ROW}.png

Open Street Map Example:
http://c.tile.openstreetmap.org/4/2/6.png
*/
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;u&gt;&lt;b&gt;ArcGIS Server Map Service:&lt;/b&gt;&lt;/u&gt;&lt;br /&gt;
&lt;br /&gt;
ArcGIS Server is the most powerful, out-of-the-box mapping server currently available.&amp;nbsp; AGS uses a similiar tile directory structure as OSM, but reverses the column and row values in the url.&amp;nbsp; ESRI is very gracious with providing access to tons of free tile services.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/4/6/2.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/4/6/2.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;/*
ArcGIS Server Tiled Map Service Template:
http://{SERVER}/tile/{ZOOM_LEVEL}/{ROW}/{COLUMN}.png

ArcGIS Server Tiled Map Service Example:
http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/4/6/2.png
*/
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;u&gt;&lt;b&gt;Google Maps&lt;/b&gt;&lt;/u&gt;&lt;br /&gt;
&lt;br /&gt;
Google uses a slightly different structure which involves url parameters.&amp;nbsp; They also include what appears to be a locale parameter which I&#39;m guessing is used to change the language of label (although I have not yet confirmed).&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;http://mt0.google.com/vt/lyrs=m@169000000&amp;amp;hl=en&amp;amp;x=2&amp;amp;y=6&amp;amp;z=4&amp;amp;s=Ga&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://mt0.google.com/vt/lyrs=m@169000000&amp;amp;hl=en&amp;amp;x=2&amp;amp;y=6&amp;amp;z=4&amp;amp;s=Ga&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;/*
Google Maps Template:
http://{SERVER}/vt/lyrs={layer_id}&amp;amp;hl={locale}&amp;amp;x={COLUMN}&amp;amp;y={ROW}&amp;amp;z={ZOOM_LEVEL}

Google Maps Example: 
http://mt0.google.com/vt/lyrs=m@169000000&amp;amp;hl=en&amp;amp;x=2&amp;amp;y=6&amp;amp;z=4&amp;amp;s=Ga
*/
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;u&gt;&lt;b&gt;Bing Map (Formerly Microsoft Virtual Earth)&lt;/b&gt;&lt;/u&gt;&lt;br /&gt;
&lt;br /&gt;
Bing has an interesting way to access tile which is using a quadkey.&amp;nbsp; A &quot;quadkey&quot; is basically the position of the tile within the &quot;quadtree&quot;.&amp;nbsp; The real reason why Web Mercator become so successful is that it turns the earth into a square which can be recursively tiled by spliting tiles into 4 to obtain tiles for the next zoom level.&amp;nbsp; I will be covering how to generate quadkeys in coming posts, but for now, here is a python snippet which I used to get the tile below:&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;def tileXYZToQuadKey(x, y, z):
    quadKey = &#39;&#39;
    for i in range(z, 0, -1):
        digit = 0
        mask = 1 &amp;lt;&amp;lt; (i - 1)
        if(x &amp;amp; mask) != 0:
            digit += 1
        if(y &amp;amp; mask) != 0:
            digit += 2
        quadKey += str(digit)
    return quadKey
&lt;/code&gt;&lt;/pre&gt;&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; print tileXYZToQuadKey(2, 6, 4)
0230
&amp;gt;&amp;gt;&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
I wrote the function above based on some C# that was provided in the bing maps tiling specification which you can read more about here: &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/bb259689.aspx&quot; target=&quot;_blank&quot;&gt;Bing Maps tile specification&lt;/a&gt;.&amp;nbsp; &lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;http://t0.tiles.virtualearth.net/tiles/a0230.jpeg?g=854&amp;amp;mkt=en-US&amp;amp;token=Anz84uRE1RULeLwuJ0qKu5amcu5rugRXy1vKc27wUaKVyIv1SVZrUjqaOfXJJoI0&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://t0.tiles.virtualearth.net/tiles/a0230.jpeg?g=854&amp;amp;mkt=en-US&amp;amp;token=Anz84uRE1RULeLwuJ0qKu5amcu5rugRXy1vKc27wUaKVyIv1SVZrUjqaOfXJJoI0&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;/*
Bing Maps Tile Template:
http://{SERVER}/tiles/a{QUAD_KEY}.jpeg?token={TOKEN}&amp;amp;mkt={LOCALE}&amp;amp;g={??}

Bing Maps Tile Example:
http://t0.tiles.virtualearth.net/tiles/a0230.jpeg?g=854&amp;amp;mkt=en-US&amp;amp;token=Anz84uRE1RULeLwuJ0qKu5amcu5rugRXy1vKc27wUaKVyIv1SVZrUjqaOfXJJoI0

Awesome Explanation: http://msdn.microsoft.com/en-us/library/bb259689.aspx
*/
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;u&gt;&lt;b&gt;&lt;br /&gt;
&lt;/b&gt;&lt;/u&gt;&lt;br /&gt;
&lt;u&gt;&lt;b&gt;Ovi Maps (Yahoo! / Nokia)&lt;/b&gt;&lt;/u&gt;&lt;br /&gt;
Yahoo! and Nokia use an API called &lt;a href=&quot;http://api.maps.ovi.com/&quot; target=&quot;_blank&quot;&gt;Ovi Maps&lt;/a&gt; which I don&#39;t know much about (but I&#39;m guessing Ovi was acquired by Nokia).&amp;nbsp; The tile structure is straight forward.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;http://4.maptile.lbs.ovi.com/maptiler/v2/maptile/279af375be/normal.day/4/2/6/256/png8?lg=ENG&amp;amp;token=TrLJuXVK62IQk0vuXFzaig%3D%3D&amp;amp;requestid=yahoo.prod&amp;amp;app_id=eAdkWGYRoc4RfxVo0Z4B&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://4.maptile.lbs.ovi.com/maptiler/v2/maptile/279af375be/normal.day/4/2/6/256/png8?lg=ENG&amp;amp;token=TrLJuXVK62IQk0vuXFzaig%3D%3D&amp;amp;requestid=yahoo.prod&amp;amp;app_id=eAdkWGYRoc4RfxVo0Z4B&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;/*
Ovi Maps (Nokia and Yahoo! Maps) Template:
http://{SERVER}/maptiler/v2/maptile/???/{STYLE}/{ZOOM_LEVEL}/{COLUMN}/{ROW}/{SIZE}/{IMG_FORMAT}?token={TOKEN}&amp;amp;lg={LOCALE}

Ovi Maps (Nokia and Yahoo! Maps) Example:
http://4.maptile.lbs.ovi.com/maptiler/v2/maptile/279af375be/normal.day/4/2/6/256/png8?lg=ENG&amp;amp;token=TrLJuXVK62IQk0vuXFzaig%3D%3D&amp;amp;requestid=yahoo.prod&amp;amp;app_id=eAdkWGYRoc4RfxVo0Z4B
*/
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;b&gt;&lt;u&gt;Yandex Maps&lt;/u&gt;&lt;/b&gt;&lt;br /&gt;
&lt;b&gt;&lt;u&gt;&lt;br /&gt;
&lt;/u&gt;&lt;/b&gt;&lt;br /&gt;
Yandex has really nice maps and great to see soCal in cyrillic.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;http://vec04.maps.yandex.net/tiles?l=map&amp;amp;v=2.26.0&amp;amp;x=2&amp;amp;y=6&amp;amp;z=4&amp;amp;lang=ru-RU&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://vec04.maps.yandex.net/tiles?l=map&amp;amp;v=2.26.0&amp;amp;x=2&amp;amp;y=6&amp;amp;z=4&amp;amp;lang=ru-RU&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;/*
Yandex Maps Template:
http://vec04.maps.yandex.net/tiles?l=map&amp;amp;v=2.26.0&amp;amp;x={{X}}&amp;amp;y={{y}}&amp;amp;z={{z}}&amp;amp;lang=ru-RU

Yandex Maps Example:
&lt;pre style=&quot;border-bottom-color: rgb(153, 153, 153); border-bottom-style: dashed; border-bottom-width: 1px; border-image: initial; border-left-color: rgb(153, 153, 153); border-left-style: dashed; border-left-width: 1px; border-right-color: rgb(153, 153, 153); border-right-style: dashed; border-right-width: 1px; border-top-color: rgb(153, 153, 153); border-top-style: dashed; border-top-width: 1px; font-family: &#39;Andale Mono&#39;, &#39;Lucida Console&#39;, Monaco, fixed, monospace; overflow-x: auto; overflow-y: auto; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; padding-top: 5px; width: 661px;&quot;&gt;&lt;code&gt;http://vec04.maps.yandex.net/tiles?l=map&amp;amp;v=2.26.0&amp;amp;x=2&amp;amp;y=6&amp;amp;z=4&amp;amp;lang=ru-RU&lt;/code&gt;&lt;/pre&gt;*/
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Well that about concludes my post on map tile url schemas.&amp;nbsp; Look for my upcoming blog post where I will cover how to generate a list of tile x,y, z values for a specified zoom level based on a given extent.</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/2847517160571748334/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2012/01/onlineoffline-mapping-map-tiles-and.html#comment-form' title='300 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/2847517160571748334'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/2847517160571748334'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2012/01/onlineoffline-mapping-map-tiles-and.html' title='Online/Offline Mapping: Map Tiles and their URL Schemas'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>300</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-3557078423471481809</id><published>2011-12-11T14:00:00.000-08:00</published><updated>2011-12-11T14:00:55.968-08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ArcObjects"/><category scheme="http://www.blogger.com/atom/ns#" term="C#"/><category scheme="http://www.blogger.com/atom/ns#" term="Rasters"/><category scheme="http://www.blogger.com/atom/ns#" term="Server Object Extensions"/><category scheme="http://www.blogger.com/atom/ns#" term="Spatial Analysis"/><title type='text'>Some random ArcObjects that make writing Server Object Extensions easier...(C#)</title><content type='html'>Server Object Extensions are one of the most powerful features of ArcGIS Server.  To make writing them a bit easier for people, I am posting several of the functions I commonly use to deal with incoming data, execute an operation, and provide a response. As an example, I will take the scenario of doing a zonal statistics operation based on a user digitized polygon.  The idea is that the user selects a variable for analysis (elevation, temperature, precipitation, etc.), draws a polygon in Flex/JS/Silverlight, the polygon is sent to the server, and zonal statistics for that polygon/variable are returned.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;1. JSON to IGeometry:&lt;/b&gt; The server will receive a json object representing the polygon digitized by the user.  The first step is to convert the JSONObject to IGeometry.  I particularly liked NicoGIS&#39;s solution to the issue which is shown below:&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); color: black; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;public IGeometry ConvertAnyJsonGeometry(JsonObject jsonObjectGeometry)
        {
            object[] objArray;

            if (jsonObjectGeometry.TryGetArray(&quot;rings&quot;, out objArray))
            {
                return Conversion.ToGeometry(jsonObjectGeometry, esriGeometryType.esriGeometryPolygon);
            }

            if (jsonObjectGeometry.TryGetArray(&quot;paths&quot;, out objArray))
            {
                return Conversion.ToGeometry(jsonObjectGeometry, esriGeometryType.esriGeometryPolyline);
            }

            if (jsonObjectGeometry.TryGetArray(&quot;points&quot;, out objArray))
            {
                return Conversion.ToGeometry(jsonObjectGeometry, esriGeometryType.esriGeometryMultipoint);
            }

            try
            {
                return Conversion.ToGeometry(jsonObjectGeometry, esriGeometryType.esriGeometryPoint);
            }
            catch
            {
                try
                {
                    return Conversion.ToGeometry(jsonObjectGeometry, esriGeometryType.esriGeometryEnvelope);
                }
                catch
                {
                    return null;
                }
            }
        }&amp;nbsp; &lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;b&gt;2. IGeometry to IFeatureClass:&lt;/b&gt;&amp;nbsp; Once I have IGeometry, many times I want to convert it to an IFeatureClass so I can use it in an operation like zonal statistics. Zonal Statistics actually takes an IGeodataset, but IFeatureClass extends IGeodatatset, so all that is required is a cast. Here is a function to convert IGeometry to IFeatureClass:&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;public IFeatureClass CreateFeatureClassFromGeometry(IGeometry pGeometry, IFeatureWorkspace pOutFeatWorkspace, int wkid = 4236)
        {
            try
            {
                IFields pFields = new Fields() as IFields;
                {
                    // Set up the shape field for the feature class
                    IFieldsEdit pFieldsEdit = (IFieldsEdit)pFields;
                    IField pField = new Field();
                    IFieldEdit pFieldEdit = (IFieldEdit)pField;
                    pFieldEdit.Name_2 = &quot;Shape&quot;;
                    pFieldEdit.Type_2 = esriFieldType.esriFieldTypeGeometry;

                    IGeometryDef pGeometryDef = new GeometryDef();
                    IGeometryDefEdit pGeometryDefEdit = (IGeometryDefEdit)pGeometryDef;
                    pGeometryDefEdit.GeometryType_2 = pGeometry.GeometryType;

                    ISpatialReference pSpatialReference;
                    if (wkid == 4326)
                    {
                        ISpatialReferenceFactory2 pSpaRefFact2 = new SpatialReferenceEnvironment() as ISpatialReferenceFactory2;
                        IGeographicCoordinateSystem pGeoCoordSys = pSpaRefFact2.CreateGeographicCoordinateSystem(wkid);
                        pSpatialReference = (ISpatialReference)pGeoCoordSys;
                    }

                    else if (wkid == 102100 || wkid == 3857)
                    {
                        ISpatialReferenceFactory2 pSpaRefFact2 = new SpatialReferenceEnvironment() as ISpatialReferenceFactory2;
                        IProjectedCoordinateSystem pProjCoordSys = pSpaRefFact2.CreateProjectedCoordinateSystem(wkid);
                        pSpatialReference = (ISpatialReference)pProjCoordSys;
                    }

                    else
                    {
                        throw new ArgumentNullException(&quot;Invalid Spatial Reference Well Known Id: Please use 4326 for Geographic or 102100 for Web Mercator&quot;);
                    }

                    ISpatialReferenceResolution pSpatialReferenceResolution = (ISpatialReferenceResolution)pSpatialReference;
                    pSpatialReferenceResolution.ConstructFromHorizon();

                    pGeometryDefEdit.SpatialReference_2 = pSpatialReference;
                    pFieldEdit.GeometryDef_2 = pGeometryDef;
                    pFieldsEdit.AddField(pField);

                    // Add other required fields to the feature class

                    IObjectClassDescription pObjectClassDescription = new FeatureClassDescription();

                    for (int i = 0; i &amp;lt; pObjectClassDescription.RequiredFields.FieldCount; i++)
                    {
                        pField = pObjectClassDescription.RequiredFields.get_Field(i);
                        if (pFieldsEdit.FindField(pField.Name) == -1)
                            pFieldsEdit.AddField(pField);
                    }
                }

                // Create the feature class
                string sFeatureClassName = &quot;tmp&quot; + Guid.NewGuid().ToString(&quot;N&quot;);
                IFeatureClass pFeatureClass = pOutFeatWorkspace.CreateFeatureClass(
                   sFeatureClassName, pFields, null, null, esriFeatureType.esriFTSimple, &quot;Shape&quot;, null);

                // Add the input geometry to the feature class and return
                IFeatureCursor pFeatureCursor = pFeatureClass.Insert(true);
                IFeatureBuffer pFeatureBuffer = pFeatureClass.CreateFeatureBuffer();

                pFeatureBuffer.Shape = pGeometry;
                pFeatureCursor.InsertFeature(pFeatureBuffer);

                // Flush the feature cursor and release COM objects
                pFeatureCursor.Flush();
                Marshal.ReleaseComObject(pFeatureBuffer);
                Marshal.ReleaseComObject(pFeatureCursor);
                return pFeatureClass;
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                throw e;
            }
        }
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;b&gt;3. Create In-Memory Workspace:&lt;/b&gt;&amp;nbsp; As you notice, the function above requires a workspace.&amp;nbsp; I like to use in-memory workspaces and to create them I use the following function:&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;        public IWorkspace CreateInMemoryWorkspace()
        {
            try
            {
                // Create an InMemory workspace factory.
                IWorkspaceFactory workspaceFactory = new InMemoryWorkspaceFactory() as IWorkspaceFactory;

                // Create an InMemory geodatabase.
                IWorkspaceName workspaceName = workspaceFactory.Create(&quot;&quot;, &quot;MyWorkspace&quot;, null, 0);

                // Cast for IName.
                IName name = (IName)workspaceName;

                //Open a reference to the InMemory workspace through the name object.
                IWorkspace workspace = (IWorkspace)name.Open();
                return workspace;
            }

            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                throw e;
            }
        }
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;b&gt;4. Get Raster Dataset from Map Service:&lt;/b&gt;&amp;nbsp; Keep in mind that an SOE has access to any of the feature classes in the service which uses the SOE as a capability.&amp;nbsp; This means that you shouldn&#39;t usually need to have any hardcoded data paths in the actual code.&amp;nbsp; Rather you should try an only use data from the map service itself.&amp;nbsp; If you send in the index of the value raster the user wants to run analysis on, look how easy it is to open:&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;public IGeoDataset GetGeoDatasetByMapServiceIndex(IMapServer3 mapServer, int layerID)
        {
            IMapServerDataAccess dataAccess = (IMapServerDataAccess)mapServer;
            return dataAccess.GetDataSource(mapServer.DefaultMapName, layerID) as IGeoDataset;
        }
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;b&gt;5. Pre-loading Rasters during Init():&lt;/b&gt; Loading and unloading rasters from memory can be an expensive operation.&amp;nbsp; Fortunately with SOEs, you can load data into memory when the service starts up, and have it standing by for any request which come in.&amp;nbsp; This is different than Python geoprocessing services which basically need to load data once the actual request is receive.&amp;nbsp; One idea I&#39;ve been using is to create a data dictionary when the service starts up which keeps my value rasters standing by:&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;public Dictionary&amp;lt;int, IGeoDataset&amp;gt; CreateGeodatasetDictionary(IMapServer3 mapServer)
        {
            Dictionary&amp;lt;int, IGeoDataset&amp;gt; data_dictionary = new Dictionary&amp;lt;int, IGeoDataset&amp;gt;();

            IMapServerInfo msInfo = mapServer.GetServerInfo(mapServer.DefaultMapName);
            IMapLayerInfos layerInfos = msInfo.MapLayerInfos;
            int c = layerInfos.Count;

            for (int i = 0; i &amp;lt; c; i++)
            {
                IGeoDataset data = GetGeoDatasetByMapServiceIndex(mapServer, c);
                data_dictionary.Add(c, data);
            }

            return data_dictionary;
        }
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
Basically this dictionary is a mapping between map service indexes and their IGeodatasets which I create in the SOE Init() function and store in memory for the life of the SOE.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;6. Running Zonal Statistics: &lt;/b&gt;Now that we have both the zone dataset and the value raster at our figure tips, the zonal statistics operation is just a couple lines of code.&amp;nbsp; There are many examples of how to use the zonalops class, but here&#39;s the idea:&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;IZonalOp oZonalOp = new RasterZonalOpClass();
ITable zonalStats = oZonalOp.ZonalStatisticsAsTable(zones, valueRaster, true);
            
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;b&gt;7. ITable to IRecordSet&lt;/b&gt;: To return the results of the Zonal Stats from the SOE, you must first convert the ITable to an IRecordSet.&amp;nbsp; Here an straightforward way to do it:&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;public static IRecordSet ConvertTableToRecordset(ITable table)
        {
            IRecordSetInit recordSetInit = new ESRI.ArcGIS.Geodatabase.RecordSetClass();
            recordSetInit.SetSourceTable(table, new QueryFilterClass());
            IRecordSet recordset = recordSetInit as IRecordSet;
            return recordset;
        }
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;b&gt;8. Serialize IRecordset: &lt;/b&gt;To convert the IRecordset into return JSON, you can simply use the Conversion.ToJSON method as follows:&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;IRecordSet return_records = BSharpUtilities.ConvertTableToRecordset(areaTable);
byte[] jsonBytes = Conversion.ToJson(return_records);

return Encoding.UTF8.GetString(jsonBytes, 0, jsonBytes.Length);
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
That&#39;s about it for now.&amp;nbsp; For information on getting started with SOEs, I would check out ESRI&#39;s samples that come with the .NET SDK and also &lt;a href=&quot;http://nicogis.blogspot.com/2011/03/soe-server-object-extension-rest.html&quot;&gt;NicoGIS posts&lt;/a&gt;&amp;nbsp;</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/3557078423471481809/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2011/12/some-random-arcobjects-that-make.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/3557078423471481809'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/3557078423471481809'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2011/12/some-random-arcobjects-that-make.html' title='Some random ArcObjects that make writing Server Object Extensions easier...(C#)'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-8626942904997203371</id><published>2011-11-14T20:06:00.000-08:00</published><updated>2011-11-14T20:06:41.719-08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><title type='text'>Simple Source Code Count (Python)</title><content type='html'>Here is a simple way to count your lines of source code using python.&lt;br /&gt;
&lt;br /&gt;
The command-line tool takes a source directory and a comma-delimited list of valid file extensions.&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;#Source Lines of Code ----------------------------------------------

import os
import sys

from optparse import OptionParser

def main(source_directory, extensions):
    files = get_file_path_list(source_directory, extensions)
    counts = [get_line_count(f) for f in files]
    
    print &#39;file count: %i&#39; % len(files)
    print &#39;line count: %i&#39; % sum(counts)

def get_file_path_list(root_directory, extensions):
    files = []
    for directory, dirnames, filenames in os.walk(root_directory):
        for f in filenames:
            ext = os.path.splitext(f)[1]
            if ext in extensions:
                files.append(os.path.join(directory, f))
    return files

def get_line_count(path):
    f = open(path, &#39;r&#39;)
    lineCount = len(f.readlines())
    f.close()
    return lineCount

def parseListOption(option, opt, value, parser):
    setattr(parser.values, option.dest, value.split(&#39;,&#39;))


if __name__ == &#39;__main__&#39;:
    
    parser = OptionParser(usage=&quot;usage: %prog [options] filename&quot;,
                          version=&quot;%prog 1.0&quot;)
    
    parser.add_option(&quot;-d&quot;, &quot;--dir&quot;,
                      dest=&quot;source_directory&quot;,
                      help=&quot;Path to directory of source code&quot;)

    parser.add_option(&quot;-e&quot;, &quot;--exts&quot;,
                      type=&#39;string&#39;,
                      action=&#39;callback&#39;,
                      dest=&quot;extensions&quot;,
                      callback=parseListOption,
                      help=&quot;Path to directory of source code&quot;)
    
    (options, args) = parser.parse_args()

    main(options.source_directory, options.extensions)
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
I&#39;m on a windows environment, so I run the tool with the following command on powershell:&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;
PS &amp;gt; python countDirectoryLineCount.py -d source_directory -e extensions (.py,.as,.mxml,.cs,.java)
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;/valid&gt;</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/8626942904997203371/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2011/11/simple-source-code-count-python.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/8626942904997203371'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/8626942904997203371'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2011/11/simple-source-code-count-python.html' title='Simple Source Code Count (Python)'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-3963305889880409376</id><published>2011-10-13T18:41:00.000-07:00</published><updated>2012-05-07T20:51:53.257-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ESRI Flex API"/><category scheme="http://www.blogger.com/atom/ns#" term="Flex"/><title type='text'>Managing MapMouseEvents: Double-click zoom vs. showing infowindow (ESRI Flex API 2.4)</title><content type='html'>Apps built with the ESRI Flex API have the default behavior of zooming-in when you double-click on the map.&amp;nbsp; This is a great feature, but the double-click and single-click dispatch the same event may confuse your handler functions.&amp;nbsp; &lt;br /&gt;
&lt;ul&gt;&lt;li&gt;When you click once on the map, MapMouseEvent.MAP_CLICK is dispatched.&lt;/li&gt;
&lt;li&gt;When you double-click, MapMouseEvent.MAP_CLICK is dispatched twice before zooming begins. &lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;a href=&quot;http://help.arcgis.com/en/webapi/flex/samples/index.html#/Events/01nq00000019000000/&quot;&gt;Here is a great example of the events which are dispatched with the Flex API.&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
If you are listening to MapMouseEvent.MAP_CLICK to show an infowindow, the infowindow will open during the double-click zoom which I consider an undesirable behavior. &lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://help.arcgis.com/en/webapi/flex/samples/index.html#/Click_on_Map/01nq00000048000000/&quot;&gt;You can see the behavior I am trying to fix here.&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
The fix which I am using involves using a timer in the single-click handler to wait and see if a second MAP_CLICK event is dispatched.&amp;nbsp; If so, I assume that a double-click zoom is going on and I don&#39;t let the infowindow open.&lt;br /&gt;
&lt;br /&gt;
Here is the code from the example above modified so the infowindow does not appear during a double-click:&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;s:Application xmlns:fx=&quot;http://ns.adobe.com/mxml/2009&quot;
               xmlns:s=&quot;library://ns.adobe.com/flex/spark&quot;
               xmlns:esri=&quot;http://www.esri.com/2008/ags&quot;
               pageTitle=&quot;MapClick - click map to get current location&quot;&amp;gt;
    &amp;lt;fx:Script&amp;gt;
        &amp;lt;![CDATA[
            import com.esri.ags.events.MapMouseEvent;
            import com.esri.ags.geometry.MapPoint;
            import com.esri.ags.utils.WebMercatorUtil;
            
            /**
             * This whole timer business here is to deal with the
             * double-click zoom vs. the single click info window issue.
             * 
             * If another MAP_CLICK event is heard within the alloted time
             * then the infowindow will not open.
             */ 
            private const eventTimer:Timer = new Timer(300, 1);
            private function onMapClick(event:MapMouseEvent):void 
            {
                function onTimerComplete(timerEvent:TimerEvent):void
                {
                    this.eventTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, onTimerComplete);
                    var latlong:MapPoint = WebMercatorUtil.webMercatorToGeographic(event.mapPoint) as MapPoint;
                    myMap.infoWindow.label = &quot;You clicked at &quot;
                        + event.mapPoint.x.toFixed(1) + &quot; / &quot; + event.mapPoint.y.toFixed(1)
                        + &quot;\nLat/Long is: &quot; + latlong.y.toFixed(6)
                        + &quot; / &quot; + latlong.x.toFixed(6);
                    myMap.infoWindow.show(event.mapPoint);
                }
                
                //If timer is already running then don&#39;t show the infowindow
                if(eventTimer.running)
                {
                    this.eventTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, onTimerComplete);
                    this.eventTimer.stop();
                    return;
                }
                
                eventTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimerComplete);
                eventTimer.start();
            }
        ]]&amp;gt;
    &amp;lt;/fx:Script&amp;gt;
    
    &amp;lt;esri:Map id=&quot;myMap&quot;
              mapClick=&quot;onMapClick(event)&quot;
              scale=&quot;50000000&quot;&amp;gt;
        &amp;lt;esri:ArcGISTiledMapServiceLayer url=&quot;http://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer&quot;/&amp;gt;
    &amp;lt;/esri:Map&amp;gt;
&amp;lt;/s:Application&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
I would much rather not use a timer to achieve this.&amp;nbsp; If anybody has a better way, please let me know with a comment.</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/3963305889880409376/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2011/10/managing-mapmouseevents-double-click.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/3963305889880409376'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/3963305889880409376'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2011/10/managing-mapmouseevents-double-click.html' title='Managing MapMouseEvents: Double-click zoom vs. showing infowindow (ESRI Flex API 2.4)'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-6122248151911067047</id><published>2011-09-15T19:46:00.000-07:00</published><updated>2011-09-15T20:03:13.342-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Algorithms"/><category scheme="http://www.blogger.com/atom/ns#" term="Arrays"/><category scheme="http://www.blogger.com/atom/ns#" term="AS3"/><category scheme="http://www.blogger.com/atom/ns#" term="Flex"/><category scheme="http://www.blogger.com/atom/ns#" term="Sorting"/><title type='text'>Sorting an array on multiple fields of a nested object...</title><content type='html'>I recently ran into the need to sort an array of AS3 objects by multiple fields of a nested object. In JSON, the AS3 would look sort of like this:&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;[

    {&quot;feature&quot;:
        {&quot;attributes&quot;:{&quot;country&quot;:&quot;Brazil&quot;, &quot;state&quot;:&quot;Bahia&quot;}
        {&quot;geometry&quot;:.....}
    },
    {&quot;feature&quot;:
        {&quot;attributes&quot;:{&quot;country&quot;:&quot;Brazil&quot;, &quot;state&quot;:&quot;Sao Paulo&quot;}
        {&quot;geometry&quot;:.....}
    }
]
&lt;/code&gt;&lt;/pre&gt;Ok, so the example above is already sorted, but imagine this sort of array and needing to sort alphabetically descending first by country and then by system.  I&#39;m pretty sure that the AS3 sortOn() only handles first-level properties for each object in the array.  I chose to write a custom function which would use array.sort():&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;private function nestedTwoColumnSort(array:Array, parentField:String, 
                                     sortField1:String, sortField2:String):void
{
    function multipleSort(a:Object, b:Object):Object
    {
        var w:String = a[parentField][sortField1].toLowerCase();
        var x:String = b[parentField][sortField1].toLowerCase();
        
        //sort first field
        if(w &amp;lt; x)
        {
            return -1;
        }
        
        if(w &amp;gt; x)
        {
            return 1;
        }
        
        //if first field is the same, then sort on second field
        if(w == x)
        {
            var y:String = a[parentField][sortField2].toLowerCase();
            var z:String = b[parentField][sortField2].toLowerCase();
            
            if(y &amp;lt; z)
            {
                return -1;
            }
            
            if(y &amp;gt; z)
            {
                return 1;
            }
        }
        
        return 0;
    }
    
    //Sort once for the first field
    array.sort(multipleSort);
    
    //Sort once for second field
    array.sort(multipleSort);
}
&lt;/code&gt;&lt;/pre&gt;The nestedTwoColumnSort function takes the array, the name of the nested object, and the two fields you wish to sort by. It then runs the sort once to fully sort the first field, then again banking on all first fields to be the same.&lt;br /&gt;
&lt;br /&gt;
Here&#39;s the actual function where I call the sort function:&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); color: black; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;private function onFeaturesLoadComplete(event:AppEvent):void
{
    var features:Array = event.data as Array;
    this.nestedTwoColumnSort(features, &#39;attributes&#39;, &#39;country&#39;, &#39;system&#39;);

    blah blah blah....
&lt;/code&gt;&lt;/pre&gt;There very well might be a better way to do this,&amp;nbsp; but this  was pretty straightforward to implement and I&#39;m not seeing any noticeable performance issues in the application (but it also only runs  once in the app...).&amp;nbsp; &lt;br /&gt;
&lt;br /&gt;
I would love to hear how others accomplish the same task.</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/6122248151911067047/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2011/09/sorting-array-on-multiple-fields-of.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/6122248151911067047'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/6122248151911067047'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2011/09/sorting-array-on-multiple-fields-of.html' title='Sorting an array on multiple fields of a nested object...'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-5823864613303768893</id><published>2011-08-16T19:46:00.000-07:00</published><updated>2011-08-16T19:49:02.774-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Clean Code"/><category scheme="http://www.blogger.com/atom/ns#" term="Design Patterns"/><category scheme="http://www.blogger.com/atom/ns#" term="Flex"/><category scheme="http://www.blogger.com/atom/ns#" term="JavaScript"/><title type='text'>Singletonitis</title><content type='html'>I am a self-loathing user of the Singleton design pattern.&amp;nbsp; Global states make small scale development much less painful, especially when maintaining the state of a user interface in Flex.&lt;br /&gt;
&lt;br /&gt;
Misko Hevery gives a great explanation of why the Singleton design pattern is really an anti-pattern.&amp;nbsp; I am continuing to use Singletons for now, but I think it&#39;s time to learn Robotlegs or another dependency injection framework for ActionScript...&lt;br /&gt;
&lt;br /&gt;
&lt;iframe width=&quot;710&quot; height=&quot;500&quot; src=&quot;http://www.youtube.com/embed/-FRm3VPhseI&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/5823864613303768893/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2011/08/singletonitis.html#comment-form' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/5823864613303768893'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/5823864613303768893'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2011/08/singletonitis.html' title='Singletonitis'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img.youtube.com/vi/-FRm3VPhseI/default.jpg" height="72" width="72"/><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-6065672086124935569</id><published>2011-08-03T11:19:00.000-07:00</published><updated>2012-05-07T20:52:12.556-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ArcGIS Server"/><title type='text'>Cannot Log into REST Admin (AGS 10)</title><content type='html'>I was dealing with an annoying issue today involving logging into the REST admin area of ArcGIS Server 10.&amp;nbsp; Basically I would log in, nothing would happen and no error message would be displayed.&lt;br /&gt;
&lt;br /&gt;
For anybody having this issue, it turns out it was related to the domain of the user&#39;s account.&amp;nbsp; Basically if the user account is not local, but instead on a different domain, they will not be able to log into the ArcGIS Server 10 REST admin interface.&amp;nbsp; This is a &lt;a href=&quot;http://support.esri.com/en/bugs/nimbus/TklNMDU5NDQy&quot;&gt;known bug #NIM059442&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Upgrade to the latest service pack and it should fix the problem.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://www.blogger.com/goog_1248744048&quot;&gt;&lt;br /&gt;
&lt;/a&gt;</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/6065672086124935569/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2011/08/cannot-log-into-rest-admin-ags-10.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/6065672086124935569'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/6065672086124935569'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2011/08/cannot-log-into-rest-admin-ags-10.html' title='Cannot Log into REST Admin (AGS 10)'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-2449007898452744649</id><published>2011-07-24T19:00:00.000-07:00</published><updated>2012-05-07T20:53:31.751-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Flash"/><category scheme="http://www.blogger.com/atom/ns#" term="Flex"/><category scheme="http://www.blogger.com/atom/ns#" term="Mobile"/><title type='text'>Metro DC Flash User&#39;s Group</title><content type='html'>This weekend I had the pleasure of attending a meeting of the &lt;a href=&quot;http://www.meetup.com/dc-flash/&quot;&gt;Metro DC Flash User&#39;s Group&lt;/a&gt;.&amp;nbsp; The meeting was held at Center for Digital Imaging Arts of Boston University (satellite location) in Georgetown, DC.&lt;br /&gt;
&lt;br /&gt;
Doug Chaplow first presented on developing for mobile devices with Flash.&amp;nbsp; One of the most important points I took away from his presentation was that AIR for iOS (not really AIR) and AIR for Android now have basically all the same capabilities. For a while, Adobe had been focused on Android during the political turmoil with Apple.&amp;nbsp; Once Apple removed restricts on the cross-compiled apps, Adobe began work again on the cross-compiler for iOS and its now supposedly just as robust as AIR for Android.&lt;br /&gt;
&lt;br /&gt;
One gentlemen in the group also mentioned that if you are writing an app for both iOS (Apple), do not include the word &quot;Android&quot; or &quot;Blackberry&quot; in your code or else it will get rejected by Apple.&lt;br /&gt;
&lt;br /&gt;
Example of what &lt;b&gt;NOT&lt;/b&gt; to do in your flash mobile app:&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;switch(OS_NAME)
{
    case &#39;Android&#39;:
        result = &#39;Get Rejected From Apple Store&#39;;
        break;
    default:
        result = &#39;Get Rejected From Apple Store&#39;;
        break;
}
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
Can anybody confirm if this is true?&amp;nbsp; I wonder if this also applies to comments like:&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;//Developing for Android is better than for iOS
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
Don Anderson gave the second presentation on his MVC framework called Rivet.&amp;nbsp; While the framework is still in development, it looks like the key goal was event listener management.&amp;nbsp; In Rivet, it looked like all event listeners for a view are added and removed in a separate controller when a view is activated/deactivated.&amp;nbsp; The architecture was definitely more pure ActionScript focused than Flex.&amp;nbsp; Overall, the presentation was very good and I always welcome any talk on a variation of the Model-View-Controller architecture.&lt;br /&gt;
&lt;br /&gt;
I would really urge anybody in the DC metro area to check out &lt;a href=&quot;http://www.meetup.com/dc-flash/&quot;&gt;this meetup group&lt;/a&gt;.&amp;nbsp; It was a great time and everybody was very friendly and knowledgeable.</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/2449007898452744649/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2011/07/metro-dc-flash-users-group.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/2449007898452744649'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/2449007898452744649'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2011/07/metro-dc-flash-users-group.html' title='Metro DC Flash User&#39;s Group'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-1670131154942090288</id><published>2011-07-21T08:24:00.001-07:00</published><updated>2011-07-21T17:32:37.037-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><title type='text'>import this</title><content type='html'>&amp;gt;&amp;gt;&amp;gt; import this&lt;br /&gt;
The Zen of Python, by Tim Peters&lt;br /&gt;
&lt;br /&gt;
Beautiful is better than ugly.&lt;br /&gt;
Explicit is better than implicit.&lt;br /&gt;
Simple is better than complex.&lt;br /&gt;
Complex is better than complicated.&lt;br /&gt;
Flat is better than nested.&lt;br /&gt;
Sparse is better than dense.&lt;br /&gt;
Readability counts.&lt;br /&gt;
Special cases aren&#39;t special enough to break the rules.&lt;br /&gt;
Although practicality beats purity.&lt;br /&gt;
Errors should never pass silently.&lt;br /&gt;
Unless explicitly silenced.&lt;br /&gt;
In the face of ambiguity, refuse the temptation to guess.&lt;br /&gt;
There should be one-- and preferably only one --obvious way to do it.&lt;br /&gt;
Although that way may not be obvious at first unless you&#39;re Dutch.&lt;br /&gt;
Now is better than never.&lt;br /&gt;
Although never is often better than *right* now.&lt;br /&gt;
If the implementation is hard to explain, it&#39;s a bad idea.&lt;br /&gt;
If the implementation is easy to explain, it may be a good idea.&lt;br /&gt;
Namespaces are one honking great idea -- let&#39;s do more of those!</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/1670131154942090288/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2011/07/never-gets-old.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/1670131154942090288'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/1670131154942090288'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2011/07/never-gets-old.html' title='import this'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-2821980491207098864</id><published>2011-07-18T14:32:00.000-07:00</published><updated>2011-07-19T21:11:47.432-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ArcGIS Server"/><category scheme="http://www.blogger.com/atom/ns#" term="Flex"/><category scheme="http://www.blogger.com/atom/ns#" term="Geometry"/><category scheme="http://www.blogger.com/atom/ns#" term="JavaScript"/><title type='text'>Setting Feature Layer maxAllowableOffset property</title><content type='html'>Derek Swingley from ESRI recently enlightened me on the use of feature layers for the ArcGIS APIs for Flex, JavaScript, and Silverlight.&lt;br /&gt;
&lt;br /&gt;
Max Allowable Offset basically determines the level of geometry generalization when returned from the mapping server.&amp;nbsp; The larger the offset, the greater the simplification.&amp;nbsp; To calculate the appropriate maxAllowableOffset, I am simply using the ground resolution of the current zoom level of the map (AS3):&lt;br /&gt;
&lt;pre style=&quot;font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%&quot;&gt;&lt;code&gt;private const EARTH_CIRCUM:Number = 2 * 3.14159265 * 6378137;
private function computeAllowableOffset():void
{
    fLayer.maxAllowableOffset = (EARTH_CIRCUM / (256 * Math.pow(2, oMap.level)));
}
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
If you are using a WGS84 Basemap, you can just divide the result above by the number of meters per degree:&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%&quot;&gt;&lt;code&gt;private const EARTH_CIRCUM:Number = 2 * 3.14159265 * 6378137;
private function computeAllowableOffset():void
{
    fLayer.maxAllowableOffset = (EARTH_CIRCUM / (256 * Math.pow(2, oMap.level))) / (EARTH_CIRCUM / 360);
}
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
These functions make it so that at the equator there should only be one vertex per pixel.  I&#39;ve been wanting a little more stylized features, so I&#39;ve been adding about 35 - 40% additional offset:&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%&quot;&gt;&lt;code&gt;private const EARTH_CIRCUM:Number = 2 * 3.14159265 * 6378137;
private function computeAllowableOffset():void
{
    fLayer.maxAllowableOffset = (EARTH_CIRCUM / (256 * Math.pow(2, oMap.level))) * 1.35;
}
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
For more information on feature layers, check out &lt;a href=&quot;http://blogs.esri.com/Dev/blogs/arcgisserver/archive/2011/06/13/Feature-layers-can-generalize-geometries-on-the-fly.aspx&quot;&gt;Derek&#39;s blog posts&lt;/a&gt;</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/2821980491207098864/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2011/07/setting-feature-layer.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/2821980491207098864'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/2821980491207098864'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2011/07/setting-feature-layer.html' title='Setting Feature Layer maxAllowableOffset property'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-9091578434333742863</id><published>2011-07-02T12:16:00.000-07:00</published><updated>2012-05-07T20:55:07.801-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ArcGIS Server"/><category scheme="http://www.blogger.com/atom/ns#" term="ArcObjects"/><category scheme="http://www.blogger.com/atom/ns#" term="C#"/><category scheme="http://www.blogger.com/atom/ns#" term="Server Object Extensions"/><title type='text'>Great REST-based Server Object Extension posts from NicoGIS</title><content type='html'>NicoGIS is an awesome GIS blog.&amp;nbsp; Over the past year, I&#39;ve been learning REST-based Server Object Extensions for ArcGIS Server 10.&amp;nbsp; &lt;a href=&quot;http://nicogis.blogspot.com/search/label/SOE&quot;&gt;NicoGIS&lt;/a&gt; has some one of the best posts on the subject.&amp;nbsp; I ended up reading the post in Italian for fun, but I bet Google can translate it.</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/9091578434333742863/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2011/07/great-rest-based-server-object.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/9091578434333742863'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/9091578434333742863'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2011/07/great-rest-based-server-object.html' title='Great REST-based Server Object Extension posts from NicoGIS'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-8894598967755294725</id><published>2011-06-26T20:21:00.000-07:00</published><updated>2012-05-07T20:53:01.899-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ESRI Developer Summit"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><title type='text'>ESRI Developer Summit Python Presentation</title><content type='html'>Back in March, I had the pleasure of attending the ESRI Developer Summit in Palm Springs where I gave the following presentation on &lt;a href=&quot;http://proceedings.esri.com/library/userconf/devsummit11/videos/video81.html&quot;&gt;Becoming a Python Developer&lt;/a&gt;...&lt;br /&gt;
&lt;br /&gt;
I came to Python as a GIS Analyst without much experience in coding.&amp;nbsp; I stayed mostly within the arcgisscripting module and only later ventured into the Python Standard Library and third-party libraries.&lt;br /&gt;
&lt;br /&gt;
In the video linked above, I talk a focused aspects of Python which GIS Analysts using the language should be aware of and how to get a development environment up and running.&amp;nbsp;</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/8894598967755294725/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2011/06/esri-developer-summit-python.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/8894598967755294725'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/8894598967755294725'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2011/06/esri-developer-summit-python.html' title='ESRI Developer Summit Python Presentation'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-4796435517158009600</id><published>2011-06-26T20:13:00.000-07:00</published><updated>2012-05-07T20:54:35.574-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ArcGIS Server"/><category scheme="http://www.blogger.com/atom/ns#" term="C#"/><category scheme="http://www.blogger.com/atom/ns#" term="Geoprocessing"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><category scheme="http://www.blogger.com/atom/ns#" term="Server Object Extensions"/><title type='text'>Python Geoprocessing Vs. C# Server Object Extensions (AGS 10)</title><content type='html'>The following deals with development of geoprocessing services specifically for &lt;b&gt;ArcGIS Server 10&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://www.screencast.com/users/bcdc/folders/Jing/media/02b42fea-adb8-4845-919d-ba619798905b&quot;&gt;Introduction Video I made for Penn State&#39;s MGIS Program&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Over the past weeks, I have written several server-side tools to support web mapping applications as part of my capstone project for Penn State&#39;s MGIS Program.&amp;nbsp; The following is a general overview of what I&#39;ve found to be the strengths and weaknesses of&amp;nbsp; Geoprocessing Services in Python and Server Object Extensions in C#.&lt;br /&gt;
&lt;br /&gt;
Over the coming weeks, I will be releasing the code and discussions of the individual tools in separate posts.&amp;nbsp; &lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Geoprocessing Services (Python) &lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
For developers of web-based geoprocessing tools for ArcGIS Server 10,&amp;nbsp; the arcpy Python library is now a popular alternative to the more complex ArcObjects library.&amp;nbsp; With arcpy, you can access a decent percent of the ArcGIS tools with a clear syntax for linking them together. Knowledge of object-oriented programming is not required, and you can integrate GIS with an entire world of Python third-party libraries.&amp;nbsp; To web-enable arcpy, scripts are added to toolboxes which get published to the server.&lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;color: #38761d;&quot;&gt;Python Pros &lt;/div&gt;&lt;ul style=&quot;color: #38761d;&quot;&gt;&lt;li&gt;Accelerated development time&lt;/li&gt;
&lt;li&gt;ArcGIS includes many Python code samples&lt;/li&gt;
&lt;li&gt;Easy to debug business logic &lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;span style=&quot;color: black;&quot;&gt;In spite of their ease of development and clear syntax, aspects of python geoprocessing services limit their power.&amp;nbsp; First is that they execute slower that the equivalent code written in .NET ArcObjects. For desktop tools that may not be a problem, but for the web this can negatively impacts user experience.&amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: black;&quot;&gt; Even if speed was not an issue, geoprocessing services are more cumbersome to move from one environment to another (e.g. staging -&amp;gt; production) because access to required source data &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt; is more tightly coupled with the &lt;/span&gt;&lt;span style=&quot;color: black;&quot;&gt;tool.&amp;nbsp; Take the example of a server-side point clusterer.&amp;nbsp; To access the point feature class containing un-clustered points, a python script may use either of the following logic:&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBY9UhpyVNNwZsSzajWbIOywwtZQ44ihMgeYtCzzI93vJU0Y9N81d3tAkF7d-pYj5zpRtIPhXzKwT0KkcrCM85yzYduBczoRvtnO7KDJTRHYoeoWNOez-v8TF4Um8Q1RLwPfm7JrjeBUop/s1600/PythonPath.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;103&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBY9UhpyVNNwZsSzajWbIOywwtZQ44ihMgeYtCzzI93vJU0Y9N81d3tAkF7d-pYj5zpRtIPhXzKwT0KkcrCM85yzYduBczoRvtnO7KDJTRHYoeoWNOez-v8TF4Um8Q1RLwPfm7JrjeBUop/s640/PythonPath.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;span style=&quot;color: black;&quot;&gt;&lt;br /&gt;
&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;color: black;&quot;&gt;This first example uses a hard-coded absolute path that will need to be changed if the tool moves to an environment with a different directory structure.&amp;nbsp; A better choice would be the relative path example, but there is still hard-coding of the dataset&#39;s geodatabase and name.&amp;nbsp; With either choice, the tool suffers from a lack of true encapsulation which ends up making it harder to move and reuse on other point layers.&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: black;&quot;&gt;An aspect of the ArcGIS workflow for creating scripting tools which hinders a tool&#39;s flexibility is the need for explicit registration of input and output parameters in a properties dialog.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;color: black;&quot;&gt;&lt;br /&gt;
&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0-ribDQB6-mO7XmcXP10xRClcv94-SLe_ADgVQUFgMEgYoqT9op28ASHknItO7maMXPlehQEaxhLjGgFlJsT89_t0_T0UuTeRVMeVqW_nqmMML5PoevR9U_izWWSCyqebJcq2Ri3KUknN/s1600/propertiesWindow.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0-ribDQB6-mO7XmcXP10xRClcv94-SLe_ADgVQUFgMEgYoqT9op28ASHknItO7maMXPlehQEaxhLjGgFlJsT89_t0_T0UuTeRVMeVqW_nqmMML5PoevR9U_izWWSCyqebJcq2Ri3KUknN/s1600/propertiesWindow.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;span style=&quot;color: black;&quot;&gt;&lt;br /&gt;
&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;br /&gt;
&lt;span style=&quot;color: #e06666;&quot;&gt;Python Cons &lt;/span&gt;&lt;br /&gt;
&lt;ul&gt;&lt;li style=&quot;color: #e06666;&quot;&gt;Slower execution speed when compared to C#&lt;/li&gt;
&lt;li style=&quot;color: #e06666;&quot;&gt;Tighter coupling with source data&lt;/li&gt;
&lt;li style=&quot;color: #e06666;&quot;&gt;Double managing of service input/output parameters&lt;/li&gt;
&lt;/ul&gt;When using geoprocessing services, the developer gains development time by sacrificing execution speed, and the fine-grain control offered by ArcObjects.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;C# Server Object Extensions (ArcObjects 10 .NET SDK)&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;color: #38761d;&quot;&gt;C# Pros &lt;/div&gt;&lt;ul style=&quot;color: #38761d;&quot;&gt;&lt;li&gt;Faster code execution&lt;/li&gt;
&lt;li&gt;Loose coupling with tool source data&lt;/li&gt;
&lt;li&gt;Easy to deploy across environments&lt;/li&gt;
&lt;/ul&gt;I began using .NET ArcObjects because python geoprocessing service felt sluggish.&amp;nbsp; The allure of greater speed justified learning a more complex language.&amp;nbsp; What I found was not only increased speed, but also easier deployment and lower maintenance. &lt;br /&gt;
&lt;br /&gt;
The idea of the Server Object Extension is that instead of publishing a service on top of the server, you extend the server itself.&amp;nbsp; This extended functionality can then be enabled for any service along with other out-of-the-box capabilities (e.g. Export Map, Identify, Find, Query, Generate KML, WMS). &lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnrY3jDLXLRRX2q2UJj0c7ERQncGAHTaDr0XmYbxVmjWUuyApZOdeLWwvwlTlU-WYGI74uqwliGdjXz1pbp0pdcmgaWC3AeAWKxw3MUsjqHKlY97wdpbJLno_qLkfhliOzveyjGXaqv4nP/s1600/AGS_Capabilities.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;323&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnrY3jDLXLRRX2q2UJj0c7ERQncGAHTaDr0XmYbxVmjWUuyApZOdeLWwvwlTlU-WYGI74uqwliGdjXz1pbp0pdcmgaWC3AeAWKxw3MUsjqHKlY97wdpbJLno_qLkfhliOzveyjGXaqv4nP/s400/AGS_Capabilities.png&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
The beautiful thing about Server Object Extensions is that ArcObjects has hooks into the source data within a map service.&amp;nbsp; These hooks give C# access to datasets using map service layer indexes instead of directory paths allowing for looser coupling with the server environment. ESRI Web APIs also use map service layer indexes which makes client-server communication easier.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiiLi6rW1LoYSRUuVbPsxKzSirKTY_l1GyPYVN9a_XLxjfBf5CaM4iUQHHUjS4o4l20MTEsRuZZMuRer3KIt1n5vGMXF97oezGUTlPKUnjWTucTpnZhNV2Fv4Yntv5-y-_jOgJrXBd-1Isc/s1600/GetGeoDatasetByIndex.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;102&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiiLi6rW1LoYSRUuVbPsxKzSirKTY_l1GyPYVN9a_XLxjfBf5CaM4iUQHHUjS4o4l20MTEsRuZZMuRer3KIt1n5vGMXF97oezGUTlPKUnjWTucTpnZhNV2Fv4Yntv5-y-_jOgJrXBd-1Isc/s640/GetGeoDatasetByIndex.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;color: #e06666;&quot;&gt;C# Cons &lt;/span&gt;&lt;br /&gt;
&lt;ul&gt;&lt;li style=&quot;color: #e06666;&quot;&gt;Large and complex library&lt;/li&gt;
&lt;li style=&quot;color: #e06666;&quot;&gt;Not Cross-Platform (won&#39;t run on AGS Java version).&lt;/li&gt;
&lt;li style=&quot;color: #e06666;&quot;&gt;Steeper learning curve&lt;/li&gt;
&lt;/ul&gt;Writing SOEs requires knowledge of a more complex library of tools accessed through a more complex programming language.&amp;nbsp; If a project does not have somebody with ArcObjects experience, diving into Server Object Extensions will be intimidating. In ArcObjects, finding the core methods for geoprocessing is easy, but getting data into a class which implements the correct interface for use as input in those methods can be difficult.&lt;br /&gt;
&lt;br /&gt;
An example of this would be getting a geometry object, passed in as JSON from the client, into a feature class which can be used by a Spatial Analyst tool.&amp;nbsp; The solution I used took 80 lines of not fun code to write.&amp;nbsp; The image below is meant to give an impression of the complexity.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4HtwK470iA1ZfkQkUTlBFnW7tb76M4_lysJupeSxcvt5_o1MCLA4nroT6ObBOlIQnCUkbVuKWmrGLNfDX-wBgnpMFqK_BtoPQk3gc11QpnRmxinDg5rXGeKmyIIewCM5G8HEx8Dm9ydL-/s1600/Geometry2FeatureClass.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4HtwK470iA1ZfkQkUTlBFnW7tb76M4_lysJupeSxcvt5_o1MCLA4nroT6ObBOlIQnCUkbVuKWmrGLNfDX-wBgnpMFqK_BtoPQk3gc11QpnRmxinDg5rXGeKmyIIewCM5G8HEx8Dm9ydL-/s1600/Geometry2FeatureClass.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Another area of trouble is .NET and inability to run outside of a Window&#39;s environment.&amp;nbsp; Being that ESRI offer&#39;s a Java version of the ArcGIS Server, choosing C# means limiting a tool to Window&#39;s and the .NET version of ArcGIS Server.&amp;nbsp; This hasn&#39;t been a major issue for me yet, but I want as many options in hosting environments as possible.&lt;br /&gt;
&lt;br /&gt;
Overall I have found the benefits of Server Object Extensions outweigh the ease of development offered by&amp;nbsp; Python when performance and reusablilty are important. Initially, I found the complexity of working in ArcObjects discouraging, but slowly I wrote function to deal this data type conversions and development got easier.&amp;nbsp; I now consider arcpy as a solution only on the desktop.&lt;br /&gt;
&lt;span style=&quot;color: black;&quot;&gt;&lt;br /&gt;
&lt;/span&gt;</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/4796435517158009600/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2011/06/python-geoprocessing-vs-c-server-object.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/4796435517158009600'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/4796435517158009600'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2011/06/python-geoprocessing-vs-c-server-object.html' title='Python Geoprocessing Vs. C# Server Object Extensions (AGS 10)'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBY9UhpyVNNwZsSzajWbIOywwtZQ44ihMgeYtCzzI93vJU0Y9N81d3tAkF7d-pYj5zpRtIPhXzKwT0KkcrCM85yzYduBczoRvtnO7KDJTRHYoeoWNOez-v8TF4Um8Q1RLwPfm7JrjeBUop/s72-c/PythonPath.png" height="72" width="72"/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-8436187189808705387</id><published>2011-06-26T15:01:00.000-07:00</published><updated>2011-06-26T15:01:05.028-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Flex"/><category scheme="http://www.blogger.com/atom/ns#" term="Pixel Bender"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><category scheme="http://www.blogger.com/atom/ns#" term="Rasters"/><category scheme="http://www.blogger.com/atom/ns#" term="Where Camp"/><title type='text'>Where Camp DC</title><content type='html'>I had a bunch of fun at Where Camp DC&#39;s ignite talks.&amp;nbsp; The format was a 5 minute presentation of 20 auto-incrementing slides (15s per slide).&amp;nbsp; Somehow I gained a second chin and a lisp in the process...&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe allowfullscreen=&#39;allowfullscreen&#39; webkitallowfullscreen=&#39;webkitallowfullscreen&#39; mozallowfullscreen=&#39;mozallowfullscreen&#39; width=&#39;320&#39; height=&#39;266&#39; src=&#39;https://www.youtube.com/embed/1XoTxGBJMPo?feature=player_embedded&#39; frameborder=&#39;0&#39;&gt;&lt;/iframe&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/8436187189808705387/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2011/06/where-camp-dc.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/8436187189808705387'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/8436187189808705387'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2011/06/where-camp-dc.html' title='Where Camp DC'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-1252525327559025979</id><published>2010-09-13T22:36:00.000-07:00</published><updated>2012-05-07T20:55:46.031-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ArcGIS Server"/><category scheme="http://www.blogger.com/atom/ns#" term="ArcObjects"/><category scheme="http://www.blogger.com/atom/ns#" term="C#"/><category scheme="http://www.blogger.com/atom/ns#" term="Server Object Extensions"/><title type='text'>Find Watershed SOE</title><content type='html'>&amp;nbsp;The Find Watershed Server Object Extension (SOE) plugs into ArcGIS Server 10 and adds functionality for generating watersheds over REST.&amp;nbsp; The SOE takes two parameters: hydroshed id and a pour point location.&amp;nbsp; Proper flow accumulation and flow direction rasters are retrieved by hydroshed id from a file geodatabase.&amp;nbsp; The location parameter is the pour point.&amp;nbsp; The goal of the tool is to generate a polygon representing the area of water which flows &lt;b&gt;into &lt;/b&gt;a point.&amp;nbsp; &lt;br /&gt;
&lt;br /&gt;
I like the motto, &quot;if you can&#39;t do in on desktop, you won&#39;t be able to do it on server.&quot;&amp;nbsp; Keeping this idea in mind, I first wrote the tool as a add-in for ArcMap. This allowed me to debug core business logic for the tool without the complications of the server.&lt;br /&gt;
&lt;br /&gt;
The main difference between a &#39;desktop tool&#39; and Server Object Extension is the ability to deserialize requests and serialize responses over http.&amp;nbsp; Since I was new to C# and Server Object Extensions, I first published a sample SOE called SimpleRESTSOE from the samples folder of the ArcGIS 10 install directory. This tool was simply an echo service (i.e. pass a string in, and get the string back).&amp;nbsp;&amp;nbsp; Requests declare &#39;text&#39; and &#39;f&#39; (output format) parameters as part of a url request:&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;/echo?&lt;/span&gt;&lt;b style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;text=&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;helloWorld&amp;amp;&lt;/span&gt;&lt;b style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;f&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;=json&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
and they SOE responds:&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;{&quot;text&quot;:&quot;helloWorld&quot;}&amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;&lt;br /&gt;
&lt;/span&gt;&lt;br /&gt;
I got the sample SOE registered with Windows using the command:&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;RegAsm SimpleRESTSOE.dll /codebase&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
I then registered the SOE with ArcCatalog and the Server Manager using the companion file located in the samples folder (&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;RegisterSimpleRESTSOE.dll&lt;/span&gt;).&lt;br /&gt;
&lt;br /&gt;
Once I verified the sample SOE was running, I simply started modifing the rest schema to take my custom parameters.&amp;nbsp; To do this, I modified the CreateRestSchema method of the COM class for the tool.&amp;nbsp; I also added some additional variables to support my business logic:&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYdBanMSCGsa_8Es9AWkFLfGjXvrLMrF9hA322AS1ELkAz-r3QNjgcguVG_ZO9ciL7VYcur1YTU-m_vuVoKfe2EyLSVYQ9ivZaKZJpRleWzDxKr6T-FbExcbcmpPNkO37EuOvwDsmxwgXM/s1600/create_schema.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;538&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYdBanMSCGsa_8Es9AWkFLfGjXvrLMrF9hA322AS1ELkAz-r3QNjgcguVG_ZO9ciL7VYcur1YTU-m_vuVoKfe2EyLSVYQ9ivZaKZJpRleWzDxKr6T-FbExcbcmpPNkO37EuOvwDsmxwgXM/s640/create_schema.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; font-family: inherit; text-align: left;&quot;&gt;After modifying the CreateRestSchema method the tool was wired to take the following request:&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;/echo?&lt;/span&gt;&lt;b style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;hydroshed_id&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;=sa7&amp;amp;&lt;/span&gt;&lt;b style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;location&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;={&quot;x&quot;=-54,&quot;y&quot;=-24}&amp;amp;&lt;/span&gt;&lt;b style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;f&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&gt;=json&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;
I wrapped my business logic in a function and called it from the REST operation&#39;s handler EchoInput. THe EchoInput method parses the incoming request, calculates the watershed, and serialize the output.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjN1aQKWvK_DO6RPuF0DzL6_rg4gA4REJAFM89D8YMLjRuvQBf9pucIEtsQ1To3xaNawb6y1ZQEOQ100Si8NSC7wmtIvRQpGpXz_l_AJFTXj1WbTbcHJlk7x0UYat0gzB_TsqnPsPhMFmos/s1600/handle_operations.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;446&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjN1aQKWvK_DO6RPuF0DzL6_rg4gA4REJAFM89D8YMLjRuvQBf9pucIEtsQ1To3xaNawb6y1ZQEOQ100Si8NSC7wmtIvRQpGpXz_l_AJFTXj1WbTbcHJlk7x0UYat0gzB_TsqnPsPhMFmos/s640/handle_operations.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;There is a block of 3 lines towards the bottom that comprise the business logic from my ArcMap tool.&amp;nbsp; The first two are helper functions (CreateInMemoryWorkspace / CreateFCFromGeometry), and the third is the actual watershed operation:&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhphso9dsGqeNM-UjMB_9LQP6DjbuDpvBcENoHbQfK_bAnfF6Ge2wLKrcLZDh_Tvtk1vknk-dWmNzRwi88WjTiY9W355m0nL8iOM6O-c-fE-AB2iiQ0iqDNQyUIKs9ZVN2_yucA0MxmbCL7/s1600/compute_watershed.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;508&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhphso9dsGqeNM-UjMB_9LQP6DjbuDpvBcENoHbQfK_bAnfF6Ge2wLKrcLZDh_Tvtk1vknk-dWmNzRwi88WjTiY9W355m0nL8iOM6O-c-fE-AB2iiQ0iqDNQyUIKs9ZVN2_yucA0MxmbCL7/s640/compute_watershed.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgh4y7nvEQicWfZbZ6J5JBSmHQmyZygr_Bk33xUBkxNt5ftIQzQG48ZV_jAC68ceD1Bl71bg278h9xUw0sHG9aupDRkDCHvWdFxPJOMgE3F0fjo4BCj52L2cTRQue4hyphenhyphenJfFS_aijKo3h4jJ/s1600/hydroshed_id_lookup.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;329&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgh4y7nvEQicWfZbZ6J5JBSmHQmyZygr_Bk33xUBkxNt5ftIQzQG48ZV_jAC68ceD1Bl71bg278h9xUw0sHG9aupDRkDCHvWdFxPJOMgE3F0fjo4BCj52L2cTRQue4hyphenhyphenJfFS_aijKo3h4jJ/s640/hydroshed_id_lookup.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;Global Hydrosheds Lookup&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;
I built a small client application to test Find Watershed over the web. In the app, the user first supplies the location by clicking on the map.&amp;nbsp; The click location is passes to the server into a query against the lookup map to determine the hydroshed id then :&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfR-I3DbTh1QuBzsJ11Kp70GLZSlWaPxZho-Yq3gT3aSEszfKv24PnkBT0NQEMDage2SxzCE0FxjTmNgaXO_FeASMCz4HKO4wrB23QepbHDlIB35u20nDz_gmgb2S2nlII2nbXEkhp-csJ/s1600/send_to_findwatershed.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;350&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfR-I3DbTh1QuBzsJ11Kp70GLZSlWaPxZho-Yq3gT3aSEszfKv24PnkBT0NQEMDage2SxzCE0FxjTmNgaXO_FeASMCz4HKO4wrB23QepbHDlIB35u20nDz_gmgb2S2nlII2nbXEkhp-csJ/s640/send_to_findwatershed.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgh4y7nvEQicWfZbZ6J5JBSmHQmyZygr_Bk33xUBkxNt5ftIQzQG48ZV_jAC68ceD1Bl71bg278h9xUw0sHG9aupDRkDCHvWdFxPJOMgE3F0fjo4BCj52L2cTRQue4hyphenhyphenJfFS_aijKo3h4jJ/s1600/hydroshed_id_lookup.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;br /&gt;
&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsfTnaJtbBP5JAooUhZ8RJ1k96WqIwziooqypqpldK08_sGhg1twi-tUd9_REW8Rq9kLvcEolSO28Y8rnOdku_sxnVbaxl8xict_AgohNxpLKWpVy_XvSe9Bttui43mpkanGB9ZODFJREy/s1600/SOE_benchmarking.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsfTnaJtbBP5JAooUhZ8RJ1k96WqIwziooqypqpldK08_sGhg1twi-tUd9_REW8Rq9kLvcEolSO28Y8rnOdku_sxnVbaxl8xict_AgohNxpLKWpVy_XvSe9Bttui43mpkanGB9ZODFJREy/s1600/SOE_benchmarking.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b style=&quot;font-family: Arial,Helvetica,sans-serif;&quot;&gt;building a client&lt;/b&gt;</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/1252525327559025979/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2010/09/find-watershed-soe.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/1252525327559025979'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/1252525327559025979'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2010/09/find-watershed-soe.html' title='Find Watershed SOE'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYdBanMSCGsa_8Es9AWkFLfGjXvrLMrF9hA322AS1ELkAz-r3QNjgcguVG_ZO9ciL7VYcur1YTU-m_vuVoKfe2EyLSVYQ9ivZaKZJpRleWzDxKr6T-FbExcbcmpPNkO37EuOvwDsmxwgXM/s72-c/create_schema.png" height="72" width="72"/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-603208590136948311.post-4507813312833801458</id><published>2010-08-25T23:13:00.001-07:00</published><updated>2012-05-07T20:56:18.000-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ArcMap"/><category scheme="http://www.blogger.com/atom/ns#" term="ArcObjects"/><category scheme="http://www.blogger.com/atom/ns#" term="C#"/><title type='text'>Site Selection with ArcObjects</title><content type='html'>In continuing my application development class through Penn State, I wrote a small site selection tool using C# and ArcObjects.  The interesting part of that the application was stand-alone in contrast to the ArcMap tools we wrote in weeks past. I made it through the practice exercises no problem, but ran into some issues when writing the deliverable for the week.  Here are some of those problems and solutions:&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Referencing Map Layers&lt;/b&gt;&lt;br /&gt;
One of the bugs in my app which gave me the most trouble was related the order in which layers are loaded into the application.  The problem was caused by the following code:&lt;br /&gt;
&lt;code&gt;&lt;br /&gt;
            this.axMapControl1.AddLayerFromFile(dataPath + &quot;counties.lyr&quot;);&lt;br /&gt;
            pCntyLayer = this.axMapControl1.get_Layer(0) as IFeatureLayer;&lt;br /&gt;
            pCntyFC = pCntyLayer.FeatureClass;&lt;br /&gt;
&lt;br /&gt;
            this.axMapControl1.AddLayerFromFile(dataPath + &quot;recareas.lyr&quot;);&lt;br /&gt;
            pRecLayer = this.axMapControl1.get_Layer(1) as IFeatureLayer;&lt;br /&gt;
            pRecFC = pRecLayer.FeatureClass;&lt;br /&gt;
&lt;br /&gt;
            this.axMapControl1.AddLayerFromFile(dataPath + &quot;interstates.lyr&quot;);&lt;br /&gt;
            pInterLayer = this.axMapControl1.get_Layer(2) as IFeatureLayer;&lt;br /&gt;
            pInterFC = pInterLayer.FeatureClass;&lt;br /&gt;
&lt;br /&gt;
            this.axMapControl1.AddLayerFromFile(dataPath + &quot;cities.lyr&quot;);&lt;br /&gt;
            pCityLayer = this.axMapControl1.get_Layer(3) as IFeatureLayer;&lt;br /&gt;
            pCityFC = pCityLayer.FeatureClass;&lt;br /&gt;
&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Notice the index values in the get_Layer function.  I initially expected the layer indexes to be the same as the order in which they were added to the map.  This turned out to be wrong.  Actually each layer was added to the beginning of the array, meaning that for the code above, each of the indexes should be 0.&lt;br /&gt;
&lt;br /&gt;
This bug through me on a wild goose chance because it turned out I got the first layer index correct, so I didn&#39;t get a error message until I was further into writing application logic. In the code above, each of the layers will be the county layer.  By coincidence this was the first layer I was doing operations one which cause additional confusion when operations on the cities layer didn&#39;t work.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Creating the Geometry Bag&lt;/b&gt;&lt;br /&gt;
When creating a geometry bag of features from Query Filter, I first attempted writting my own functions but quick found that the professors solution was more elegant.  I translated the VBA code into the following C# function:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;&lt;br /&gt;
private IGeometryBag getGeomBag(IQueryFilter pQueryFilter, IFeatureClass pFClass)&lt;br /&gt;
        {&lt;br /&gt;
            IEnumGeometry pEnumGeom = new EnumFeatureGeometryClass();&lt;br /&gt;
            IEnumGeometryBind pEnumGeomBind = pEnumGeom as IEnumGeometryBind;&lt;br /&gt;
            pEnumGeomBind.BindGeometrySource(pQueryFilter, pFClass);&lt;br /&gt;
            IGeometryFactory pGeomFactory = new GeometryEnvironmentClass();&lt;br /&gt;
            return pGeomFactory.CreateGeometryFromEnumerator(pEnumGeom) as IGeometryBag;&lt;br /&gt;
        }&lt;br /&gt;
&lt;/code&gt; &lt;br /&gt;
&lt;br /&gt;
This function applies the filter and return a nice geometry for additional site selection steps.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Conclusion&lt;/b&gt;&lt;br /&gt;
While I got my stand alone application working, I was unable to integrate it into excel and powerpoint.  I am currently experimenting with VSTO (Visual Studio Tools for Office) to see if I find a solution.</content><link rel='replies' type='application/atom+xml' href='http://bcdcspatial.blogspot.com/feeds/4507813312833801458/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://bcdcspatial.blogspot.com/2010/08/site-selection-with-arcobjects.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/4507813312833801458'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/603208590136948311/posts/default/4507813312833801458'/><link rel='alternate' type='text/html' href='http://bcdcspatial.blogspot.com/2010/08/site-selection-with-arcobjects.html' title='Site Selection with ArcObjects'/><author><name>Brendan</name><uri>http://www.blogger.com/profile/16270967680111675817</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>