0

I´m trying to make a tool that would be able to compare 2 layers of any type (polygon, point, polyline) and find differences between them. Lets say i have a layer of roads (Polyline) from 2005 (layer X) and from 2015 (layer Y). This tool would check those layers and mark the roads missing in layer X (field Change = 1), roads missing in layer Y (field Change = -1) and the roads without change (field Change = 0). Note that matching objects between those layers by IDs might be uncorrect, I decided to make their own unique key.

So far I have a field named Geo_IDx and y (for both layers), that holds geometry token "SHAPE@JSON", created by arcpy.UpdateCursor:

    cursor2 = arcpy.da.UpdateCursor(Input2, [fieldnames2[0], "SHAPE@JSON", "Geo_IDx"])

Here comes the first issue: the string saved in "Geo_IDx" field is very long (currently set for 5000 signs, because I don't want to lose part of the geometry information).

  • Do you have any advice about how to make some better unique key for the comparison?

Another problem is that this comparison takes very long time for layers with big amount of objects stored in them.

Then I Merge the layers to have all those values in 1 layer, that holds the field "Change". After this I am comparing the "Geo_ID" fields from both layers, provided that if value from layer X matches with any value in layer Y, there was no change in that object. If the value is missing in either of those layers, something has changed, so by cursors I find in which layer the value is missing and update the Change value as mentioned above.

Following script shows the way I´m trying to update the "Change" field. geo1 and geo2 are lists of all values from Geo_IDx => geo1 and Geo_IDy => geo2.

    cursor3 = arcpy.da.UpdateCursor(Output,[f_names_merge[0], "Geo_IDx", "Geo_IDy", "Change"])
    for row in cursor3:
        if row[0]:
            if row[1] in geo2:
                row[3] = -1
            elif row[2] in geo1:
                row[3] = 1
            else:
                row[3] = 0
            cursor3.updateRow(row)

What I think the script is failing on is comparing the geometry strings, that are saved in Geo_ID fields. I dont have any idea why.

Questions:

  • Do you have any better idea of unique key for every object?
  • Is there any easier way to do this? (provided I´m using ArcEditor license).
  • Is it possible to compare the values of "SHAPE@JSON"?

I do not have access to FME.

  • If you have access to FME, try the Change Detector transformer – Nxaunxau Apr 13 '16 at 13:57
  • If you're solely focused on geometry, have you considered putting both featureclasses in a topology, then look at the errors that violate a "must overlap each other" rule? – Kirk Kuykendall Apr 13 '16 at 17:06
2

If I understand your code right, you are using a JSON representation of your geometry to compare old and new geometry. If python can't match the string to your list of strings, it then adds a -1 to your change field.

I think using a json representation, or WKT or string, to compare geometry for changes might get tricky. What if the change was a reordering of the points where the start and end points flipped, but no actual change to the location of points? Probably a rare example, but this could cause problems.

Some none ArcPy (possible) solutions:

Use Topology. You have an ArcEditor license so it should be available to you. You can probably use the Must Be Covered By Feature Class Of rule to find pairs of lines that are different. http://resources.arcgis.com/en/help/main/10.2/index.html#//0017000000nn000000 http://resources.arcgis.com/en/help/main/10.2/index.html#//01mm0000000m000000

Use Select By Location, ARE_IDENTICAL_TO to detect lines that are not identical. Then populate your field for selected features. This may be a slow option, however.

http://resources.arcgis.com/en/help/main/10.2/index.html#//001700000072000000

ArcPy Solutions:

I think to really detect change you would need to get to the geometry. Once you get the geometry object you can use Equals() to compare two geometries. Use the select by location tool to limit which geometry you are going to compare (you don't want to loop through every feature to compare one road).

http://resources.arcgis.com/en/help/main/10.2/index.html#//001700000072000000

Here is the equals method: http://resources.arcgis.com/en/help/main/10.2/index.html#/Point/018z0000006t000000/

I also found some suggestions here (note the problems with version 10.0) that use the JSON representation if you prefer to use that:

How can one compare two geometries in arcpy successfully?

  • You are partly correct about the change field.. its set to -1 if the object is missing / changed in "new" layer, +1 if the object is missing/ changed in "old" layer and 0 if there is no change. As to the topology rule Must be covered by FC Of:, I´m not sure it would work on continuous polygon layers? I understand that rule the way that its not "error" as long as it is overlapped. Note that i used the roads just as example of what i´m dealing with. As to JSON, this information was stored in TEXT field (converted to String) – Marek Hauner Apr 14 '16 at 8:15
  • There are similar rules for polygons, see the link above. – dslamb Apr 14 '16 at 10:17
  • Seems I managed to solve the problem by comparing dictionaries consisting of IDs and JSON representation...its quite fast and so far it works the way it should. Thanks for your help @dslamb – Marek Hauner Apr 14 '16 at 13:40
1

ArcGIS Desktop already has a tool that does this. It's called Feature Compare.

You can limit it to only compare the Geometries. You could use the output from this tool to further scrutinize the geometry. For example, if Feature Compare finds geometry differences for features with ObjectID 142 (in both feature classes), you could then pump those features into the Symmetrical Difference tool to show you visually exactly where the differences between those suspect features are.

If there are a number of features with differences, you would want to iterate through all of them with Symmetrical Difference, and then aggregate all those output files into a single file that could then be added to the map.

  • It looks there is also a Detect Feature Changes in teh same toolbox. Good to know. resources.arcgis.com/en/help/main/10.2/index.html#//… – dslamb Apr 13 '16 at 19:08
  • Yeah, I saw that one too but was disappointed it only works with lines. But if you work with lines, that looks like a sweet tool. – alexGIS Apr 13 '16 at 19:54
  • Yeah i tried working with Feature Compare and Symm. Difference, but it appeared for me to be "buggy" while used on continuous polygon layers. – Marek Hauner Apr 14 '16 at 7:42
  • Those tools are fine if you feed data to them in the right way. For contiguous polygons (i.e. parcels) you won't see differences if you compare them all at the same time. But if you compare just one at a time then the results are rock solid. To do this you'll need to do it using Python. In fact it's something I've been thinking about writing for sometime, but now I think I'll do it. If you don't get an answer before I finish it, I'll post it here. – alexGIS Apr 14 '16 at 7:49
  • I was using it just the "bad" way you stated here. Thing is, that I do not have a reliable key to select the objects i want to compare. It´s not like I have 2 layers and I want to compare objectIDa 1 with objectIDb 1. I need to match together the 2 features i want to compare. That is why I was trying to create that unique Geo_ID key. Just want to mention, that I don´t have Advanced license (got Editor). – Marek Hauner Apr 14 '16 at 8:35
0

A quick and easy way to detect geometry changes is to compare Centroid locations. Feature to Point is a good tool to create Centroids from Lines or Polygons. If the closest Centroid in the compare layer is greater than .001 (whatever tolerance you choose) then the geometry changed.

  • Using centroids was my first idea, but solely centroids are not reliable, because there may be occasions, when the centroid does not change, but the shape / area does.. i was also thinking about making the unique key by combination of centroid location and shape area for polygons / length for polylines.. – Marek Hauner Apr 14 '16 at 7:17
  • I used to roads just to make an example of what i want to achieve. This tool should be able to deal with points / polylines / polygons. – Marek Hauner Apr 14 '16 at 7:44
0

You can compare two shapefiles by differencing one from the other as in this function:

    """
    ShapeDifference.py - Subtract one shape file from another
    Gene Dial (gene.dial@yahoo.com)
    """

    import arcpy as ap

    def ShapeDifference(shpA, shpB):
        """
        Modifies the polygon geometry of shape A by subtracting the polygon
        geometries of shape B. The implementation calculates the union of all
        of the polygon geometries in shape B and then subtracts that union
        from the polygon geometry of each row of shape B. Attributes are unchanged.

        Args:
            shpA (feature class): Shape A will be modified by differencing the area
                covered by shape B. The number of rows will be unchanged. It is
                possible that a row will have no area after subtracting shape B.
            shpB (feature class): All of the polygon areas in shape B are merged
                into a MULTIPOLYGON and the resulting union subtracted from each
                row of shape A. Shape B is unchanged.
        """

        # Search cursor into shape file retrieves records
        sumCursor = ap.da.SearchCursor(shpB, 'SHAPE@')
        # Merge the polygons in shpB into a MULTIPOLYGON
        polygonSum = None
        for row in sumCursor:
            shape = row[0]
            if polygonSum is None:
                polygonSum = shape
            else:
                polygonSum = polygonSum.union(shape)
        del sumCursor

        # Update cursor allows reading and updating feature rows
        difCursor = ap.UpdateCursor(shpA)
        for row in difCursor:
            shape = row.getValue('SHAPE')
            shape = shape.difference(polygonSum)
            row.setValue('SHAPE', shape)
            difCursor.updateRow(row)
        del difCursor

    """
        Test ShapeDifference
    """
    if __name__ == "__main__":
        # Modify newShape by subtracting oldShape
        newShape = r'C:\data\NewShape.shp'
        oldShape = r'C:\data\OldShape.shp'
        ShapeDifference(newShape, oldShape)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.