class GeojsonDiff
Constants
- META_KEY
- VERSION
Attributes
Public Class Methods
# File lib/geojson-diff.rb, line 15 def initialize(before, after) @before = ensure_feature_collection(RGeo::GeoJSON.decode(before, :json_parser => :json)) @after = ensure_feature_collection(RGeo::GeoJSON.decode(after, :json_parser => :json)) end
Public Instance Methods
# File lib/geojson-diff.rb, line 22 def added diff(@after,@before,"added") end
# File lib/geojson-diff.rb, line 26 def removed diff(@before,@after,"removed") end
# File lib/geojson-diff.rb, line 30 def unchanged diff(@before,@after,"unchanged") end
Private Instance Methods
Generate a feature collection representing the requested diff
before - starting decoded geojson after- end decoded geojson type - type of diff to perform, noted in each feature’s properties
For diffs, think of this as what’s in #{before} that’s not in #{after} For intersections, it’s what’s in both #{before} and {after}
returns a feature collection of the diff
# File lib/geojson-diff.rb, line 125 def diff(before, after, type="difference") features = [] matched = [] # Don't mangle the true before and after instance variables before_features = before.instance_variable_get("@features").clone after_features = after.instance_variable_get("@features").clone # Loop through once diffing the properties of any directly-matched geometries # This helps eliminates edge cases where adding/removing a single element could # break the entire diff, sans an LCS approach. before_features.each_with_index do |feature, index| next unless match_index = match(feature,after_features) # direct match for a feature, on an unchanged diff, just diff properties if type == "unchanged" diffed_feature = diff_feature(feature, after_features[match_index], type) features.push diffed_feature unless diffed_feature.nil? end # If they've matched, we know they can't be added or removed after_features.delete_at(match_index) matched.push feature end # You can't delete elements from an array while iterating over them # So wait until we've matched everything, and delete those that have been matched matched.each do |feature| before_features.delete(feature) end # Loop through what's left, and perform a straight index->index geometry diff before_features.each_with_index do |feature,index| diffed_feature = diff_feature(feature, after_features[index], type) features.push diffed_feature unless diffed_feature.nil? end RGeo::GeoJSON::FeatureCollection.new(features) end
Given a feature (before and after), diffs the geometry and properties
before_feature - Feature before the change after_feature - Feature after the change type - requested diff component, either added, removed, or unchanged
Returns a feature representing the requested diff component
# File lib/geojson-diff.rb, line 66 def diff_feature(before_feature, after_feature, type="difference") geometry = diff_geometry(before_feature, after_feature, type) return nil if geometry.nil? || geometry.is_empty? properties = { META_KEY => {} } properties.merge! diff_properties(before_feature, after_feature, type) properties[META_KEY].merge!({type: type}) RGeo::GeoJSON::Feature.new(geometry,nil,properties) end
Diff the geometry of a given feature
before_feature - Feature before the change after_feature - Feature after the change type - requested diff component, either added, removed, or unchanged
Returns the resulting feature geometry
# File lib/geojson-diff.rb, line 82 def diff_geometry(before_feature, after_feature, type) if type == "unchanged" command = "intersection" else command = "difference" end if after_feature.nil? && type == "unchanged" nil # doesn't exist in other so can't intersect elsif after_feature.nil? #added or removed before_feature.geometry # pass through else # true diff before_feature.geometry.send(command, after_feature.geometry) end end
Diff the properties of a given feature
before_feature - Feature before the change after_feature - Feature after the change type - requested diff component, either added, removed, or unchanged
Returns a JSON representation of the feature’s diff’d properties
# File lib/geojson-diff.rb, line 105 def diff_properties(before_feature, after_feature, type) if after_feature.nil? && type == "unchanged" {} elsif type == "added" || type == "removed" before_feature.properties else PropertyDiff.new(before_feature.properties, after_feature.properties).properties end end
Given a geometry, ensures that it is represented as a feature collection This way diff logic can remain consistent between geometry types Rather than creating diff logic for each type
# File lib/geojson-diff.rb, line 39 def ensure_feature_collection(geometry) return geometry if geometry.class == RGeo::GeoJSON::FeatureCollection return RGeo::GeoJSON::FeatureCollection.new([]) if geometry.nil? geometry = RGeo::GeoJSON::Feature.new(geometry) unless geometry.class == RGeo::GeoJSON::Feature RGeo::GeoJSON::FeatureCollection.new([geometry]) end
Find index of indentical feature within a feature collection based on geometry
from_feature - the needle feature to - the haystack featurecollection
returns (int) the index of the identical feature, or nil
# File lib/geojson-diff.rb, line 52 def match(before_feature,after_feature_collection) after_feature_collection.find_index do |after_feature| after_feature.geometry.rep_equals?(before_feature.geometry) || after_feature.geometry.equals?(before_feature.geometry) end end