geojson-diff 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/geojson-diff.rb +163 -0
- data/lib/geojson-diff/property-diff.rb +114 -0
- data/lib/geojson-diff/version.rb +3 -0
- data/lib/rgeo/geojson.rb +10 -0
- metadata +161 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fc1a1694621504685cf1d34a82bca93fc9e7716f
|
4
|
+
data.tar.gz: d62ceaf8a1ce1eb84c6737c139d594b144fe6b15
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 676f81f290083fbb5b2870e23c42156c4774a03c8a59a90cfaa4b13ed180258f76f390ed3df0e1dc071d8a77e46b80e7c6d4cd5ddeadd21f885afc40d13fb326
|
7
|
+
data.tar.gz: e201fdb7c6897c8f3985757a98d40aa4830967eba25759b03fab89b05b8f16994fe31e966f6469297616dd3a6ff7838a329968fed2b1e2f09eaff42d8ee645d1
|
data/lib/geojson-diff.rb
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'rgeo'
|
2
|
+
require 'rgeo/geo_json'
|
3
|
+
require 'diffy'
|
4
|
+
require 'json'
|
5
|
+
require_relative 'rgeo/geojson'
|
6
|
+
require_relative 'geojson-diff/property-diff'
|
7
|
+
require_relative 'geojson-diff/version'
|
8
|
+
|
9
|
+
ENV["GEOS_LIBRARY_PATH"] ||= File.expand_path("lib", `geos-config --prefix`.strip)
|
10
|
+
|
11
|
+
class GeojsonDiff
|
12
|
+
|
13
|
+
META_KEY = '_geojson_diff'
|
14
|
+
|
15
|
+
def initialize(before, after)
|
16
|
+
@before = ensure_feature_collection(RGeo::GeoJSON.decode(before, :json_parser => :json))
|
17
|
+
@after = ensure_feature_collection(RGeo::GeoJSON.decode(after, :json_parser => :json))
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_accessor :before, :after
|
21
|
+
|
22
|
+
def added
|
23
|
+
diff(@after,@before,"added")
|
24
|
+
end
|
25
|
+
|
26
|
+
def removed
|
27
|
+
diff(@before,@after,"removed")
|
28
|
+
end
|
29
|
+
|
30
|
+
def unchanged
|
31
|
+
diff(@before,@after,"unchanged")
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Given a geometry, ensures that it is represented as a feature collection
|
37
|
+
# This way diff logic can remain consistent between geometry types
|
38
|
+
# Rather than creating diff logic for each type
|
39
|
+
def ensure_feature_collection(geometry)
|
40
|
+
return geometry if geometry.class == RGeo::GeoJSON::FeatureCollection
|
41
|
+
return RGeo::GeoJSON::FeatureCollection.new([]) if geometry.nil?
|
42
|
+
geometry = RGeo::GeoJSON::Feature.new(geometry) unless geometry.class == RGeo::GeoJSON::Feature
|
43
|
+
RGeo::GeoJSON::FeatureCollection.new([geometry])
|
44
|
+
end
|
45
|
+
|
46
|
+
# Find index of indentical feature within a feature collection based on geometry
|
47
|
+
#
|
48
|
+
# from_feature - the needle feature
|
49
|
+
# to - the haystack featurecollection
|
50
|
+
#
|
51
|
+
# returns (int) the index of the identical feature, or nil
|
52
|
+
def match(before_feature,after_feature_collection)
|
53
|
+
after_feature_collection.find_index do |after_feature|
|
54
|
+
after_feature.geometry.rep_equals?(before_feature.geometry) ||
|
55
|
+
after_feature.geometry.equals?(before_feature.geometry)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Given a feature (before and after), diffs the geometry and properties
|
60
|
+
#
|
61
|
+
# before_feature - Feature before the change
|
62
|
+
# after_feature - Feature after the change
|
63
|
+
# type - requested diff component, either added, removed, or unchanged
|
64
|
+
#
|
65
|
+
# Returns a feature representing the requested diff component
|
66
|
+
def diff_feature(before_feature, after_feature, type="difference")
|
67
|
+
geometry = diff_geometry(before_feature, after_feature, type)
|
68
|
+
return nil if geometry.nil? || geometry.is_empty?
|
69
|
+
properties = { META_KEY => {} }
|
70
|
+
properties.merge! diff_properties(before_feature, after_feature, type)
|
71
|
+
properties[META_KEY].merge!({type: type})
|
72
|
+
RGeo::GeoJSON::Feature.new(geometry,nil,properties)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Diff the geometry of a given feature
|
76
|
+
#
|
77
|
+
# before_feature - Feature before the change
|
78
|
+
# after_feature - Feature after the change
|
79
|
+
# type - requested diff component, either added, removed, or unchanged
|
80
|
+
#
|
81
|
+
# Returns the resulting feature geometry
|
82
|
+
def diff_geometry(before_feature, after_feature, type)
|
83
|
+
if type == "unchanged"
|
84
|
+
command = "intersection"
|
85
|
+
else
|
86
|
+
command = "difference"
|
87
|
+
end
|
88
|
+
|
89
|
+
if after_feature.nil? && type == "unchanged"
|
90
|
+
nil # doesn't exist in other so can't intersect
|
91
|
+
elsif after_feature.nil? #added or removed
|
92
|
+
before_feature.geometry # pass through
|
93
|
+
else # true diff
|
94
|
+
before_feature.geometry.send(command, after_feature.geometry)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Diff the properties of a given feature
|
99
|
+
#
|
100
|
+
# before_feature - Feature before the change
|
101
|
+
# after_feature - Feature after the change
|
102
|
+
# type - requested diff component, either added, removed, or unchanged
|
103
|
+
#
|
104
|
+
# Returns a JSON representation of the feature's diff'd properties
|
105
|
+
def diff_properties(before_feature, after_feature, type)
|
106
|
+
if after_feature.nil? && type == "unchanged"
|
107
|
+
{}
|
108
|
+
elsif type == "added" || type == "removed"
|
109
|
+
before_feature.properties
|
110
|
+
else
|
111
|
+
PropertyDiff.new(before_feature.properties, after_feature.properties).properties
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Generate a feature collection representing the requested diff
|
116
|
+
#
|
117
|
+
# before - starting decoded geojson
|
118
|
+
# after- end decoded geojson
|
119
|
+
# type - type of diff to perform, noted in each feature's properties
|
120
|
+
#
|
121
|
+
# For diffs, think of this as what's in #{before} that's not in #{after}
|
122
|
+
# For intersections, it's what's in both #{before} and {after}
|
123
|
+
#
|
124
|
+
# returns a feature collection of the diff
|
125
|
+
def diff(before, after, type="difference")
|
126
|
+
features = []
|
127
|
+
matched = []
|
128
|
+
|
129
|
+
# Don't mangle the true before and after instance variables
|
130
|
+
before_features = before.instance_variable_get("@features").clone
|
131
|
+
after_features = after.instance_variable_get("@features").clone
|
132
|
+
|
133
|
+
# Loop through once diffing the properties of any directly-matched geometries
|
134
|
+
# This helps eliminates edge cases where adding/removing a single element could
|
135
|
+
# break the entire diff, sans an LCS approach.
|
136
|
+
before_features.each_with_index do |feature, index|
|
137
|
+
next unless match_index = match(feature,after_features)
|
138
|
+
|
139
|
+
# direct match for a feature, on an unchanged diff, just diff properties
|
140
|
+
if type == "unchanged"
|
141
|
+
diffed_feature = diff_feature(feature, after_features[match_index], type)
|
142
|
+
features.push diffed_feature unless diffed_feature.nil?
|
143
|
+
end
|
144
|
+
|
145
|
+
# If they've matched, we know they can't be added or removed
|
146
|
+
after_features.delete_at(match_index)
|
147
|
+
matched.push feature
|
148
|
+
end
|
149
|
+
|
150
|
+
# You can't delete elements from an array while iterating over them
|
151
|
+
# So wait until we've matched everything, and delete those that have been matched
|
152
|
+
matched.each do |feature|
|
153
|
+
before_features.delete(feature)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Loop through what's left, and perform a straight index->index geometry diff
|
157
|
+
before_features.each_with_index do |feature,index|
|
158
|
+
diffed_feature = diff_feature(feature, after_features[index], type)
|
159
|
+
features.push diffed_feature unless diffed_feature.nil?
|
160
|
+
end
|
161
|
+
RGeo::GeoJSON::FeatureCollection.new(features)
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
class GeojsonDiff
|
2
|
+
class PropertyDiff
|
3
|
+
|
4
|
+
# Creates a new PropertyDiff instance
|
5
|
+
#
|
6
|
+
# before - the property element of the starting geometry (as parsed JSON)
|
7
|
+
# after - the property element of the resulting geometry (as parsed JSON)
|
8
|
+
#
|
9
|
+
# returns the PropertyDiff instance
|
10
|
+
def initialize(before,after)
|
11
|
+
@before = before
|
12
|
+
@after = after
|
13
|
+
@meta = { :added => [], :removed => [], :changed => [] }
|
14
|
+
diff
|
15
|
+
end
|
16
|
+
|
17
|
+
# Checks if the given key has been added to the resulting geometry
|
18
|
+
#
|
19
|
+
# key - string the JSON property key to check
|
20
|
+
#
|
21
|
+
# Returns bool true if added, otherwise false
|
22
|
+
def added?(key)
|
23
|
+
!@before.key?(key) && @after.key?(key)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns an array of all keys added to the resulting geometry
|
27
|
+
def added
|
28
|
+
@meta[:added]
|
29
|
+
end
|
30
|
+
|
31
|
+
# Checks if the given key has been removed from the resulting geometry
|
32
|
+
#
|
33
|
+
# key - string the JSON property key to check
|
34
|
+
#
|
35
|
+
# Returns bool true if removed, otherwise false
|
36
|
+
def removed?(key)
|
37
|
+
@before.key?(key) && !@after.key?(key)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns an array of all keys removed from the resulting geometry
|
41
|
+
def removed
|
42
|
+
@meta[:removed]
|
43
|
+
end
|
44
|
+
|
45
|
+
# Checks if the given key has been modified in the resulting geometry
|
46
|
+
#
|
47
|
+
# key - string the JSON property key to check
|
48
|
+
#
|
49
|
+
# Returns bool true if modified, otherwise false
|
50
|
+
def changed?(key)
|
51
|
+
!added?(key) && !removed?(key) && @before[key] != @after[key]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns an array of all keys modified in the resulting geometry
|
55
|
+
def changed
|
56
|
+
@meta[:changed]
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns the JSON representation of the diffed properties, including metadata
|
60
|
+
def to_json
|
61
|
+
diff.to_json
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_s
|
65
|
+
diff.to_s
|
66
|
+
end
|
67
|
+
|
68
|
+
def inspect
|
69
|
+
"#<GeojsonDiff::PropertyDiff added=#{added.to_s} removed=#{removed.to_s} changed=#{changed.to_s}>"
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns a hash of the diffed properties, including metadata
|
73
|
+
def diff
|
74
|
+
@diff ||= begin
|
75
|
+
properties = {}
|
76
|
+
@before.merge(@after).each { |key,value| properties.merge! diffed_property(key) }
|
77
|
+
properties.merge({ GeojsonDiff::META_KEY => @meta })
|
78
|
+
end
|
79
|
+
end
|
80
|
+
alias_method :properties, :diff
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
# Diffs an individual key/value pair
|
85
|
+
# Also propegates @meta arrays
|
86
|
+
#
|
87
|
+
# key - the property key to diff
|
88
|
+
#
|
89
|
+
# Returns the resulting key/value pair, either before (removed), after (added), or diffed (changed)
|
90
|
+
def diffed_property(key)
|
91
|
+
if added? key
|
92
|
+
@meta[:added].push key
|
93
|
+
{ key => @after[key] }
|
94
|
+
elsif removed? key
|
95
|
+
@meta[:removed].push key
|
96
|
+
{ key => @before[key] }
|
97
|
+
elsif changed? key
|
98
|
+
@meta[:changed].push key
|
99
|
+
{ key => diffed_value(key) }
|
100
|
+
else # unchanged
|
101
|
+
{ key => @after[key] }
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Diffs an indivual changed value
|
106
|
+
#
|
107
|
+
# key - the property key to diff
|
108
|
+
#
|
109
|
+
# Returns (string) the Diffy representation of the changed property
|
110
|
+
def diffed_value(key)
|
111
|
+
Diffy::Diff.new(@before[key], @after[key]).to_s(:html)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/lib/rgeo/geojson.rb
ADDED
metadata
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: geojson-diff
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ben Balter
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rgeo
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rgeo-geojson
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: ffi-geos
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.5'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: diffy
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.3'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.3'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: mocha
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: bundler
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.5'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.5'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: pry
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.9'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.9'
|
125
|
+
description: GeoJSON Diff takes two GeoJSON files representing the same geometry (or
|
126
|
+
geometries) at two points in time, and generates three GeoJSON files represented
|
127
|
+
the added, removed, and unchanged geometries.
|
128
|
+
email: ben.balter@github.com
|
129
|
+
executables: []
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- lib/geojson-diff.rb
|
134
|
+
- lib/geojson-diff/property-diff.rb
|
135
|
+
- lib/geojson-diff/version.rb
|
136
|
+
- lib/rgeo/geojson.rb
|
137
|
+
homepage: https://github.com/benbalter/geojson-diff
|
138
|
+
licenses:
|
139
|
+
- MIT
|
140
|
+
metadata: {}
|
141
|
+
post_install_message:
|
142
|
+
rdoc_options: []
|
143
|
+
require_paths:
|
144
|
+
- lib
|
145
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - ">="
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
requirements: []
|
156
|
+
rubyforge_project:
|
157
|
+
rubygems_version: 2.2.2
|
158
|
+
signing_key:
|
159
|
+
specification_version: 4
|
160
|
+
summary: A Ruby library for diffing GeoJSON files
|
161
|
+
test_files: []
|