mapplz 0.1.3 → 0.1.4
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 +4 -4
- data/README.md +23 -9
- data/lib/mapplz.rb +566 -302
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4da45e726fdc80c0a4ce530e6522f31ed8c8035f
|
4
|
+
data.tar.gz: 5595cad9a95f0ec4b638c183d6f8f6c268ff4bab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d204b6cc48b3803adb797ff19dbf9c15b4187b2db01dc13852264761abd7e4148f478e423414ef7f81ce8081aebc4a79b48d98aa6187c315f19d119f19c1669
|
7
|
+
data.tar.gz: 6e050eea53d3d0f6fd844eb0844267618fbde3a5130c9424a967126bcc99345e024fde465f34437b66cecfd6027b89a07495d6b9d5b3aa2314c08d77000d5685
|
data/README.md
CHANGED
@@ -110,6 +110,13 @@ mapplz.where('layer = ?', name_of_layer)
|
|
110
110
|
# get a count
|
111
111
|
mapplz.count
|
112
112
|
mapplz.count('layer = ?', name_of_layer)
|
113
|
+
|
114
|
+
# near a point
|
115
|
+
mapplz.near([lat, lng])
|
116
|
+
mapplz.near([lat, lng], max: 10)
|
117
|
+
|
118
|
+
# in an area
|
119
|
+
mapplz.inside([point1, point2, point3, point1])
|
113
120
|
```
|
114
121
|
|
115
122
|
Queries are returned as an array of GeoItems, which each can be exported as GeoJSON or WKT
|
@@ -119,6 +126,21 @@ my_features = @mapper.where('points > 10')
|
|
119
126
|
collection = { type: 'FeatureCollection', features: my_features.map { |feature| JSON.parse(feature.to_geojson) } }
|
120
127
|
```
|
121
128
|
|
129
|
+
## Files
|
130
|
+
|
131
|
+
MapPLZ can be passed a CSV or GeoJSON file.
|
132
|
+
|
133
|
+
```
|
134
|
+
@mapstore < File.open('test.csv')
|
135
|
+
@mapstore < File.open('test.geojson')
|
136
|
+
```
|
137
|
+
|
138
|
+
If you have gdal installed, you can import files in most formats parseable by the ```ogr2ogr``` command line tool.
|
139
|
+
|
140
|
+
```
|
141
|
+
@mapstore < File.open('test.shp')
|
142
|
+
```
|
143
|
+
|
122
144
|
## Databases
|
123
145
|
|
124
146
|
You can store geodata in SQLite/Spatialite, Postgres/PostGIS, or MongoDB.
|
@@ -147,6 +169,7 @@ require 'mongo'
|
|
147
169
|
mongo_client = Mongo::MongoClient.new
|
148
170
|
database = mongo_client['mapplz']
|
149
171
|
collection = database['geoitems']
|
172
|
+
collection.create_index(geo: Mongo::GEO2DSPHERE)
|
150
173
|
mapstore = MapPLZ.new(collection)
|
151
174
|
mapstore.choose_db('mongodb')
|
152
175
|
|
@@ -167,15 +190,6 @@ mapstore = MapPLZ.new(db)
|
|
167
190
|
mapstore.choose_db('spatialite')
|
168
191
|
```
|
169
192
|
|
170
|
-
### COMING SOON
|
171
|
-
|
172
|
-
```
|
173
|
-
# near a point
|
174
|
-
mapplz.near([lat, lng])
|
175
|
-
|
176
|
-
# in an area
|
177
|
-
mapplz.inside([point1, point2, point3, point1])
|
178
|
-
```
|
179
193
|
|
180
194
|
## Language
|
181
195
|
You can make a map super quickly by using the MapPLZ language. A MapPLZ map
|
data/lib/mapplz.rb
CHANGED
@@ -2,6 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'sql_parser'
|
4
4
|
require 'json'
|
5
|
+
require 'csv'
|
6
|
+
require 'geokdtree'
|
7
|
+
require 'digest'
|
5
8
|
include Leaflet::ViewHelpers
|
6
9
|
|
7
10
|
# MapPLZ datastore
|
@@ -33,20 +36,33 @@ class MapPLZ
|
|
33
36
|
end
|
34
37
|
|
35
38
|
def add(user_geo, lonlat = false)
|
36
|
-
|
39
|
+
begin
|
40
|
+
geo_objects = MapPLZ.standardize_geo(user_geo, lonlat, @db)
|
41
|
+
rescue MapPLZException
|
42
|
+
geo_objects = code(user_geo)
|
43
|
+
end
|
37
44
|
|
38
45
|
if @db_type == 'array'
|
39
46
|
@my_array += geo_objects
|
40
47
|
elsif @db_type == 'mongodb'
|
41
48
|
geo_objects.each do |geo_object|
|
42
|
-
|
49
|
+
save_obj = geo_object.clone
|
50
|
+
save_obj.delete(:_id)
|
51
|
+
save_obj.delete(:lat)
|
52
|
+
save_obj.delete(:lng)
|
53
|
+
save_obj.delete(:path)
|
54
|
+
save_obj.delete(:centroid)
|
55
|
+
save_obj.delete(:type)
|
56
|
+
save_obj[:geo] = JSON.parse(geo_object.to_geojson)['geometry']
|
57
|
+
reply = @db_client.insert(save_obj)
|
43
58
|
geo_object[:_id] = reply.to_s
|
44
59
|
end
|
45
60
|
elsif @db_type == 'postgis' || @db_type == 'spatialite'
|
46
61
|
geo_objects.each do |geo_object|
|
47
62
|
geom = geo_object.to_wkt
|
48
63
|
if @db_type == 'postgis'
|
49
|
-
|
64
|
+
geojson_props = (JSON.parse(geo_object.to_geojson)['properties'] || {})
|
65
|
+
reply = @db_client.exec("INSERT INTO mapplz (properties, geom) VALUES ('#{geojson_props.to_json}', ST_GeomFromText('#{geom}')) RETURNING id")
|
50
66
|
elsif @db_type == 'spatialite'
|
51
67
|
reply = @db_client.execute("INSERT INTO mapplz (label, geom) VALUES ('#{geo_object[:label] || ''}', AsText('#{geom}')) RETURNING id")
|
52
68
|
end
|
@@ -63,20 +79,12 @@ class MapPLZ
|
|
63
79
|
|
64
80
|
def count(where_clause = nil, add_on = nil)
|
65
81
|
results = query(where_clause, add_on)
|
66
|
-
|
67
|
-
results.length
|
68
|
-
elsif @db_type == 'mongodb'
|
69
|
-
if where_clause.present?
|
70
|
-
# @db_client.find().count
|
71
|
-
else
|
72
|
-
@db_client.count
|
73
|
-
end
|
74
|
-
else
|
75
|
-
results.count
|
76
|
-
end
|
82
|
+
results.length
|
77
83
|
end
|
78
84
|
|
79
85
|
def query(where_clause = nil, add_on = nil)
|
86
|
+
geo_results = []
|
87
|
+
|
80
88
|
if where_clause.present?
|
81
89
|
if @db_type == 'array'
|
82
90
|
geo_results = query_array(where_clause, add_on)
|
@@ -96,17 +104,19 @@ class MapPLZ
|
|
96
104
|
end
|
97
105
|
|
98
106
|
cursor = @db_client.find(mongo_conditions)
|
99
|
-
elsif @db_type == 'postgis'
|
107
|
+
elsif @db_type == 'postgis'
|
108
|
+
where_prop = where_clause.strip.split(' ')[0]
|
109
|
+
where_clause = where_clause.gsub(where_prop, "json_extract_path_text(properties, '#{where_prop}')")
|
110
|
+
|
111
|
+
cursor = @db_client.exec("SELECT id, ST_AsText(geom) AS geo, properties FROM mapplz WHERE #{where_clause}")
|
112
|
+
elsif @db_type == 'spatialite'
|
100
113
|
if add_on.is_a?(String)
|
101
114
|
where_clause = where_clause.gsub('?', "'#{add_on}'")
|
102
115
|
elsif add_on.is_a?(Integer) || add_on.is_a?(Float)
|
103
116
|
where_clause = where_clause.gsub('?', "#{add_on}")
|
104
117
|
end
|
105
118
|
|
106
|
-
cursor = @db_client.
|
107
|
-
cursor = @db_client.execute("SELECT id, AsText(geom) AS geom, label FROM mapplz WHERE #{where_clause}") if @db_type == 'spatialite'
|
108
|
-
else
|
109
|
-
# @my_db.where(where_clause, add_on)
|
119
|
+
cursor = @db_client.execute("SELECT id, AsText(geom) AS geo, label FROM mapplz WHERE #{where_clause}")
|
110
120
|
end
|
111
121
|
else
|
112
122
|
# query all
|
@@ -115,47 +125,15 @@ class MapPLZ
|
|
115
125
|
elsif @db_type == 'mongodb'
|
116
126
|
cursor = @db_client.find
|
117
127
|
elsif @db_type == 'postgis'
|
118
|
-
cursor = @db_client.exec('SELECT id, ST_AsText(geom) AS
|
128
|
+
cursor = @db_client.exec('SELECT id, ST_AsText(geom) AS geo, properties FROM mapplz')
|
119
129
|
elsif @db_type == 'spatialite'
|
120
|
-
cursor = @db_client.execute('SELECT id, AsText(geom) AS
|
130
|
+
cursor = @db_client.execute('SELECT id, AsText(geom) AS geo, label FROM mapplz')
|
121
131
|
else
|
122
132
|
# @my_db.all
|
123
133
|
end
|
124
134
|
end
|
125
135
|
|
126
|
-
|
127
|
-
geo_results = []
|
128
|
-
cursor.each do |geo_result|
|
129
|
-
geo_item = GeoItem.new
|
130
|
-
geo_result.keys.each do |key|
|
131
|
-
next if [:geom].include?(key.to_sym)
|
132
|
-
geo_item[key.to_sym] = geo_result[key]
|
133
|
-
end
|
134
|
-
|
135
|
-
if @db_type == 'postgis' || @db_type == 'spatialite'
|
136
|
-
geom = (geo_result['geom'] || geo_result[:geom]).upcase
|
137
|
-
if geom.index('POINT')
|
138
|
-
coordinates = geom.gsub('POINT', '').gsub('(', '').gsub(')', '').split(' ')
|
139
|
-
geo_item[:lat] = coordinates[1].to_f
|
140
|
-
geo_item[:lng] = coordinates[0].to_f
|
141
|
-
elsif geom.index('LINESTRING')
|
142
|
-
line_nodes = geom.gsub('LINESTRING', '').gsub('(', '').gsub(')', '').split(',')
|
143
|
-
geo_item[:path] = line_nodes.map do |pt|
|
144
|
-
pt = pt.split(' ')
|
145
|
-
[pt[1].to_f, pt[0].to_f]
|
146
|
-
end
|
147
|
-
elsif geom.index('POLYGON')
|
148
|
-
line_nodes = geom.gsub('POLYGON', '').gsub('(', '').gsub(')', '').split(', ')
|
149
|
-
geo_item[:path] = line_nodes.map do |pt|
|
150
|
-
pt = pt.split(' ')
|
151
|
-
[pt[1].to_f, pt[0].to_f]
|
152
|
-
end
|
153
|
-
end
|
154
|
-
end
|
155
|
-
geo_results << geo_item
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
136
|
+
geo_results += read_cursor(cursor)
|
159
137
|
geo_results
|
160
138
|
end
|
161
139
|
|
@@ -255,12 +233,415 @@ class MapPLZ
|
|
255
233
|
render_text + '</script>'
|
256
234
|
end
|
257
235
|
|
236
|
+
def near(user_geo, limit = 10, max = 40_010_000, lon_lat = false)
|
237
|
+
max = max.to_f # send max in meters
|
238
|
+
limit = limit.to_i
|
239
|
+
|
240
|
+
if user_geo.is_a?(Hash)
|
241
|
+
lat = user_geo[:lat] || user_geo['lat'] || user_geo[:latitude] || user_geo['latitude']
|
242
|
+
lng = user_geo[:lng] || user_geo['lng'] || user_geo[:longitude] || user_geo['longitude']
|
243
|
+
user_geo = [lat.to_f, lng.to_f]
|
244
|
+
elsif user_geo.is_a?(Array)
|
245
|
+
user_geo.reverse! if lon_lat
|
246
|
+
else
|
247
|
+
fail 'must query near a point'
|
248
|
+
end
|
249
|
+
|
250
|
+
lat = user_geo[0].to_f
|
251
|
+
lng = user_geo[1].to_f
|
252
|
+
wkt = "POINT(#{lng} #{lat})"
|
253
|
+
geo_results = []
|
254
|
+
|
255
|
+
if @db_type == 'array'
|
256
|
+
@my_array.sort! do |a, b|
|
257
|
+
a_distance = a.distance_from([lat, lng])
|
258
|
+
b_distance = b.distance_from([lat, lng])
|
259
|
+
a_distance <=> b_distance
|
260
|
+
end
|
261
|
+
geo_results = @my_array.slice(0, limit)
|
262
|
+
elsif @db_type == 'mongodb'
|
263
|
+
cursor = @db_client.find(geo: { '$nearSphere' => { '$geometry' => { type: 'Point', coordinates: [lng, lat] }, '$maxDistance' => max } })
|
264
|
+
elsif @db_type == 'postgis'
|
265
|
+
cursor = @db_client.exec("SELECT id, ST_AsText(geom) AS geo, properties, ST_Distance(start.geom::geography, ST_GeomFromText('#{wkt}')::geography) AS distance FROM mapplz AS start WHERE ST_Distance(start.geom::geography, ST_GeomFromText('#{wkt}')::geography) <= #{max} ORDER BY distance LIMIT #{limit}")
|
266
|
+
elsif @db_type == 'spatialite'
|
267
|
+
cursor = @db_client.execute("SELECT id, AsText(geom) AS geo, label, Distance(start.geom, AsText('#{wkt}')) AS distance FROM mapplz AS start WHERE Distance(start.geom, AsText('#{wkt}')) <= #{max} ORDER BY distance LIMIT #{limit}")
|
268
|
+
end
|
269
|
+
|
270
|
+
geo_results += read_cursor(cursor)
|
271
|
+
geo_results
|
272
|
+
end
|
273
|
+
|
274
|
+
def inside(user_geo)
|
275
|
+
geo_results = []
|
276
|
+
|
277
|
+
# accept [point1, point2, point3, point1] as a polygon search area
|
278
|
+
if user_geo.is_a?(Array) && user_geo[0].is_a?(Array) && !user_geo[0][0].is_a?(Array)
|
279
|
+
user_geo = [user_geo]
|
280
|
+
end
|
281
|
+
|
282
|
+
search_areas = MapPLZ.standardize_geo(user_geo)
|
283
|
+
|
284
|
+
search_areas.each do |search_area|
|
285
|
+
next unless search_area.key?(:path)
|
286
|
+
wkt = search_area.to_wkt
|
287
|
+
|
288
|
+
if @db_type == 'array'
|
289
|
+
# in-Ruby point-in-polygon
|
290
|
+
@my_array.each do |geo_item|
|
291
|
+
GeoItem.centroid(geo_item)
|
292
|
+
geo_results << geo_item if geo_item.inside?(search_area)
|
293
|
+
end
|
294
|
+
elsif @db_type == 'mongodb'
|
295
|
+
polygon_gj = JSON.parse(search_area.to_geojson)['geometry']
|
296
|
+
cursor = @db_client.find(geo: { '$geoWithin' => { '$geometry' => polygon_gj } })
|
297
|
+
elsif @db_type == 'postgis'
|
298
|
+
cursor = @db_client.exec("SELECT id, ST_AsText(geom) AS geo, properties FROM mapplz AS start WHERE ST_Contains(ST_GeomFromText('#{wkt}'), start.geom)")
|
299
|
+
elsif @db_type == 'spatialite'
|
300
|
+
cursor = @db_client.exec("SELECT id, AsText(geom) AS geo, label FROM mapplz WHERE MBRContains(FromText('#{wkt}'), FromText(geom))")
|
301
|
+
end
|
302
|
+
|
303
|
+
geo_results += read_cursor(cursor)
|
304
|
+
end
|
305
|
+
geo_results
|
306
|
+
end
|
307
|
+
|
258
308
|
def self.flip_path(path)
|
259
309
|
path.map! do |pt|
|
260
310
|
[pt[1].to_f, pt[0].to_f]
|
261
311
|
end
|
262
312
|
end
|
263
313
|
|
314
|
+
def self.parse_wkt(geo_item, geom_string)
|
315
|
+
if geom_string.index('POINT')
|
316
|
+
coordinates = geom_string.gsub('POINT', '').gsub('(', '').gsub(')', '').split(' ')
|
317
|
+
geo_item[:lat] = coordinates[1].to_f
|
318
|
+
geo_item[:lng] = coordinates[0].to_f
|
319
|
+
elsif geom_string.index('LINESTRING')
|
320
|
+
line_nodes = geom_string.gsub('LINESTRING', '').gsub('(', '').gsub(')', '').split(',')
|
321
|
+
geo_item[:path] = line_nodes.map do |pt|
|
322
|
+
pt = pt.split(' ')
|
323
|
+
[pt[1].to_f, pt[0].to_f]
|
324
|
+
end
|
325
|
+
elsif geom_string.index('POLYGON')
|
326
|
+
line_nodes = geom_string.gsub('POLYGON', '').gsub('(', '').gsub(')', '').split(', ')
|
327
|
+
geo_item[:path] = line_nodes.map do |pt|
|
328
|
+
pt = pt.split(' ')
|
329
|
+
[pt[1].to_f, pt[0].to_f]
|
330
|
+
end
|
331
|
+
end
|
332
|
+
geo_item
|
333
|
+
end
|
334
|
+
|
335
|
+
def self.standardize_geo(user_geo, lonlat = false, db = nil)
|
336
|
+
return [user_geo] if user_geo.is_a?(GeoItem)
|
337
|
+
geo_objects = []
|
338
|
+
|
339
|
+
if user_geo.is_a?(File)
|
340
|
+
file_type = File.extname(user_geo)
|
341
|
+
if ['.csv', '.tsv', '.tdv', '.txt', '.geojson', '.json'].include?(file_type)
|
342
|
+
# parse this as if it were sent as a string
|
343
|
+
user_geo = user_geo.read
|
344
|
+
else
|
345
|
+
# convert this with ogr2ogr and parse as a string
|
346
|
+
begin
|
347
|
+
`ogr2ogr -f "GeoJSON" tmp.geojson #{File.path(user_geo)}`
|
348
|
+
user_geo = File.open('tmp.geojson').read
|
349
|
+
rescue
|
350
|
+
raise 'gdal was not installed, or format was not accepted by ogr2ogr'
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
if user_geo.is_a?(String)
|
356
|
+
begin
|
357
|
+
user_geo = JSON.parse(user_geo)
|
358
|
+
rescue
|
359
|
+
# not JSON - attempt CSV
|
360
|
+
begin
|
361
|
+
CSV.parse(user_geo.gsub('\"', '""'), headers: true) do |row|
|
362
|
+
geo_objects += standardize_geo(row, lonlat, db)
|
363
|
+
end
|
364
|
+
return geo_objects
|
365
|
+
rescue
|
366
|
+
# not JSON or CSV - attempt mapplz parse
|
367
|
+
raise MapPLZException, 'call code() to parse mapplz language'
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
if user_geo.is_a?(Array) && user_geo.length > 0
|
373
|
+
if user_geo[0].is_a?(Array) && user_geo[0].length > 0
|
374
|
+
if user_geo[0][0].is_a?(Array) || (user_geo[0][0].is_a?(Hash) && user_geo[0][0].key?(:lat) && user_geo[0][0].key?(:lng))
|
375
|
+
# lines and shapes
|
376
|
+
user_geo.map! do |path|
|
377
|
+
path_pts = []
|
378
|
+
path.each do |path_pt|
|
379
|
+
if lonlat
|
380
|
+
lat = path_pt[1] || path_pt[:lat]
|
381
|
+
lng = path_pt[0] || path_pt[:lng]
|
382
|
+
else
|
383
|
+
lat = path_pt[0] || path_pt[:lat]
|
384
|
+
lng = path_pt[1] || path_pt[:lng]
|
385
|
+
end
|
386
|
+
path_pts << [lat, lng]
|
387
|
+
end
|
388
|
+
|
389
|
+
# polygon border repeats first point
|
390
|
+
if path_pts[0] == path_pts.last
|
391
|
+
geo_type = 'polygon'
|
392
|
+
path_pts = [path_pts]
|
393
|
+
else
|
394
|
+
geo_type = 'polyline'
|
395
|
+
end
|
396
|
+
|
397
|
+
geoitem = GeoItem.new(db)
|
398
|
+
geoitem[:path] = path_pts
|
399
|
+
geoitem[:type] = geo_type
|
400
|
+
geoitem
|
401
|
+
end
|
402
|
+
return user_geo
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
# multiple objects being added? iterate through
|
407
|
+
if user_geo[0].is_a?(Hash) || user_geo[0].is_a?(Array)
|
408
|
+
user_geo.each do |geo_piece|
|
409
|
+
geo_objects += standardize_geo(geo_piece, lonlat, db)
|
410
|
+
end
|
411
|
+
return geo_objects
|
412
|
+
end
|
413
|
+
|
414
|
+
# first two spots are a coordinate
|
415
|
+
validate_lat = user_geo[0].to_f != 0 || user_geo[0].to_s == '0'
|
416
|
+
validate_lng = user_geo[1].to_f != 0 || user_geo[1].to_s == '0'
|
417
|
+
|
418
|
+
if validate_lat && validate_lng
|
419
|
+
geo_object = GeoItem.new(db)
|
420
|
+
geo_object[:type] = 'point'
|
421
|
+
|
422
|
+
if lonlat
|
423
|
+
geo_object[:lat] = user_geo[1].to_f
|
424
|
+
geo_object[:lng] = user_geo[0].to_f
|
425
|
+
else
|
426
|
+
geo_object[:lat] = user_geo[0].to_f
|
427
|
+
geo_object[:lng] = user_geo[1].to_f
|
428
|
+
end
|
429
|
+
else
|
430
|
+
fail 'no latitude or longitude found'
|
431
|
+
end
|
432
|
+
|
433
|
+
# assume user properties are an ordered array of values known to the user
|
434
|
+
user_properties = user_geo.drop(2)
|
435
|
+
|
436
|
+
# only one property and it's a hash? it's a hash of properties
|
437
|
+
if user_properties.length == 1 && user_properties[0].is_a?(Hash)
|
438
|
+
user_properties[0].keys.each do |key|
|
439
|
+
geo_object[key.to_sym] = user_properties[0][key]
|
440
|
+
end
|
441
|
+
else
|
442
|
+
geo_object[:properties] = user_properties
|
443
|
+
end
|
444
|
+
|
445
|
+
geo_objects << geo_object
|
446
|
+
|
447
|
+
elsif user_geo.is_a?(Hash) || user_geo.is_a?(CSV::Row)
|
448
|
+
if user_geo.is_a?(CSV::Row)
|
449
|
+
# convert CSV::Row to geo hash
|
450
|
+
geo_hash = {}
|
451
|
+
user_geo.headers.each do |header|
|
452
|
+
geo_hash[header] = user_geo[header]
|
453
|
+
end
|
454
|
+
user_geo = geo_hash
|
455
|
+
end
|
456
|
+
|
457
|
+
# check for lat and lng
|
458
|
+
validate_lat = false
|
459
|
+
validate_lat = 'lat' if user_geo.key?('lat') || user_geo.key?(:lat)
|
460
|
+
validate_lat ||= 'latitude' if user_geo.key?('latitude') || user_geo.key?(:latitude)
|
461
|
+
|
462
|
+
validate_lng = false
|
463
|
+
validate_lng = 'lng' if user_geo.key?('lng') || user_geo.key?(:lng)
|
464
|
+
validate_lng ||= 'lon' if user_geo.key?('lon') || user_geo.key?(:lon)
|
465
|
+
validate_lng ||= 'long' if user_geo.key?('long') || user_geo.key?(:long)
|
466
|
+
validate_lng ||= 'longitude' if user_geo.key?('longitude') || user_geo.key?(:longitude)
|
467
|
+
|
468
|
+
if validate_lat && validate_lng
|
469
|
+
# single hash
|
470
|
+
geo_object = GeoItem.new(db)
|
471
|
+
geo_object[:lat] = user_geo[validate_lat].to_f
|
472
|
+
geo_object[:lng] = user_geo[validate_lng].to_f
|
473
|
+
geo_object[:type] = 'point'
|
474
|
+
|
475
|
+
user_geo.keys.each do |key|
|
476
|
+
next if key == validate_lat || key == validate_lng
|
477
|
+
geo_object[key.to_sym] = user_geo[key]
|
478
|
+
end
|
479
|
+
geo_objects << geo_object
|
480
|
+
elsif user_geo.key?('path') || user_geo.key?(:path)
|
481
|
+
# try line or polygon
|
482
|
+
path_pts = []
|
483
|
+
path = user_geo['path'] if user_geo.key?('path')
|
484
|
+
path = user_geo[:path] if user_geo.key?(:path)
|
485
|
+
|
486
|
+
if path_pts[0].is_a?(Array) && path_pts[0][0].is_a?(Array)
|
487
|
+
# ring polygon
|
488
|
+
path.each do |ring|
|
489
|
+
ring.map! do |path_pt|
|
490
|
+
if lonlat
|
491
|
+
lat = path_pt[1] || path_pt[:lat]
|
492
|
+
lng = path_pt[0] || path_pt[:lng]
|
493
|
+
else
|
494
|
+
lat = path_pt[0] || path_pt[:lat]
|
495
|
+
lng = path_pt[1] || path_pt[:lng]
|
496
|
+
end
|
497
|
+
[lat, lng]
|
498
|
+
end
|
499
|
+
end
|
500
|
+
path_pts = path
|
501
|
+
else
|
502
|
+
path.each do |path_pt|
|
503
|
+
if lonlat
|
504
|
+
lat = path_pt[1] || path_pt[:lat]
|
505
|
+
lng = path_pt[0] || path_pt[:lng]
|
506
|
+
else
|
507
|
+
lat = path_pt[0] || path_pt[:lat]
|
508
|
+
lng = path_pt[1] || path_pt[:lng]
|
509
|
+
end
|
510
|
+
path_pts << [lat, lng]
|
511
|
+
end
|
512
|
+
|
513
|
+
# polygon border repeats first point
|
514
|
+
if path_pts[0] == path_pts.last
|
515
|
+
geo_type = 'polygon'
|
516
|
+
path_pts = [path_pts]
|
517
|
+
else
|
518
|
+
geo_type = 'polyline'
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
geoitem = GeoItem.new(db)
|
523
|
+
geoitem[:path] = path_pts
|
524
|
+
geoitem[:type] = geo_type
|
525
|
+
|
526
|
+
property_list = user_geo.clone
|
527
|
+
property_list = property_list[:properties] if property_list.key?(:properties)
|
528
|
+
property_list = property_list['properties'] if property_list.key?('properties')
|
529
|
+
property_list.delete(:path)
|
530
|
+
property_list.keys.each do |prop|
|
531
|
+
geoitem[prop.to_sym] = property_list[prop]
|
532
|
+
end
|
533
|
+
|
534
|
+
geo_objects << geoitem
|
535
|
+
elsif user_geo.key?('geo') || user_geo.key?(:geo)
|
536
|
+
# this key is GeoJSON or WKT
|
537
|
+
geotext = (user_geo['geo'] || user_geo[:geo])
|
538
|
+
if geotext.upcase.index('POINT(') || geotext.upcase.index('LINESTRING(') || geotext.upcase.index('POLYGON(')
|
539
|
+
# try WKT
|
540
|
+
geoitem = GeoItem.new(db)
|
541
|
+
geoitem = MapPLZ.parse_wkt(geoitem, geotext)
|
542
|
+
else
|
543
|
+
# GeoJSON
|
544
|
+
begin
|
545
|
+
geoitem = standardize_geo(JSON.parse(geotext), lonlat, db)[0]
|
546
|
+
rescue
|
547
|
+
# did not recognize format
|
548
|
+
raise 'did not recognize format in CSV geo column'
|
549
|
+
end
|
550
|
+
end
|
551
|
+
user_geo.keys.each do |key|
|
552
|
+
next if ['lat', 'lng', 'geo', 'geom', 'geojson', 'wkt', :lat, :lng, :geo, :geom, :geojson, :wkt].include?(key)
|
553
|
+
geoitem[key.to_sym] = user_geo[key]
|
554
|
+
end
|
555
|
+
geo_objects << geoitem
|
556
|
+
else
|
557
|
+
# try GeoJSON
|
558
|
+
if user_geo.key?(:type)
|
559
|
+
user_geo['type'] = user_geo[:type] || ''
|
560
|
+
user_geo['features'] = user_geo[:features] if user_geo.key?(:features)
|
561
|
+
user_geo['properties'] = user_geo[:properties] || {}
|
562
|
+
if user_geo.key?(:geometry)
|
563
|
+
user_geo['geometry'] = user_geo[:geometry]
|
564
|
+
user_geo['geometry']['type'] = user_geo[:geometry][:type]
|
565
|
+
user_geo['geometry']['coordinates'] = user_geo[:geometry][:coordinates]
|
566
|
+
end
|
567
|
+
end
|
568
|
+
if user_geo.key?('type')
|
569
|
+
if user_geo['type'] == 'FeatureCollection' && user_geo.key?('features')
|
570
|
+
# recursive onto features
|
571
|
+
user_geo['features'].each do |feature|
|
572
|
+
geo_objects += standardize_geo(feature, lonlat, db)
|
573
|
+
end
|
574
|
+
elsif user_geo.key?('geometry') && user_geo['geometry'].key?('coordinates')
|
575
|
+
# each feature
|
576
|
+
coordinates = user_geo['geometry']['coordinates']
|
577
|
+
|
578
|
+
if user_geo['geometry']['type'] == 'Point'
|
579
|
+
geo_object = GeoItem.new(db)
|
580
|
+
geo_object[:lat] = coordinates[1].to_f
|
581
|
+
geo_object[:lng] = coordinates[0].to_f
|
582
|
+
geo_object[:type] = 'point'
|
583
|
+
geo_objects << geo_object
|
584
|
+
elsif user_geo['geometry']['type'] == 'LineString'
|
585
|
+
geo_object = GeoItem.new(db)
|
586
|
+
MapPLZ.flip_path(coordinates)
|
587
|
+
geo_object[:path] = coordinates
|
588
|
+
geo_object[:type] = 'polyline'
|
589
|
+
geo_objects << geo_object
|
590
|
+
elsif user_geo['geometry']['type'] == 'Polygon'
|
591
|
+
geo_object = GeoItem.new(db)
|
592
|
+
coordinates.map! do |ring|
|
593
|
+
MapPLZ.flip_path(ring)
|
594
|
+
end
|
595
|
+
geo_object[:path] = coordinates
|
596
|
+
geo_object[:type] = 'polygon'
|
597
|
+
geo_objects << geo_object
|
598
|
+
elsif user_geo['geometry']['type'] == 'MultiPoint'
|
599
|
+
coordinates.each do |point|
|
600
|
+
geo_object = GeoItem.new(db)
|
601
|
+
geo_object[:lat] = point[1].to_f
|
602
|
+
geo_object[:lng] = point[0].to_f
|
603
|
+
geo_object[:type] = 'point'
|
604
|
+
geo_objects << geo_object
|
605
|
+
end
|
606
|
+
elsif user_geo['geometry']['type'] == 'MultiLineString'
|
607
|
+
coordinates.each do |line|
|
608
|
+
geo_object = GeoItem.new(db)
|
609
|
+
geo_object[:path] = MapPLZ.flip_path(line)
|
610
|
+
geo_object[:type] = 'polyline'
|
611
|
+
geo_objects << geo_object
|
612
|
+
end
|
613
|
+
elsif user_geo['geometry']['type'] == 'MultiPolygon'
|
614
|
+
coordinates.each do |poly|
|
615
|
+
geo_object = GeoItem.new(db)
|
616
|
+
poly.map! do |ring|
|
617
|
+
MapPLZ.flip_path(ring)
|
618
|
+
end
|
619
|
+
geo_object[:path] = poly
|
620
|
+
geo_object[:type] = 'polygon'
|
621
|
+
geo_objects << geo_object
|
622
|
+
end
|
623
|
+
end
|
624
|
+
|
625
|
+
# store properties on all generated geometries
|
626
|
+
prop_keys = {}
|
627
|
+
if user_geo.key?('properties')
|
628
|
+
user_geo['properties'].keys.each do |key|
|
629
|
+
prop_keys[key.to_sym] = user_geo['properties'][key]
|
630
|
+
end
|
631
|
+
end
|
632
|
+
geo_objects.each do |geo|
|
633
|
+
prop_keys.keys.each do |key|
|
634
|
+
geo[key] = prop_keys[key]
|
635
|
+
end
|
636
|
+
end
|
637
|
+
end
|
638
|
+
end
|
639
|
+
end
|
640
|
+
end
|
641
|
+
|
642
|
+
geo_objects
|
643
|
+
end
|
644
|
+
|
264
645
|
# alias methods
|
265
646
|
|
266
647
|
# aliases for add
|
@@ -293,9 +674,14 @@ class MapPLZ
|
|
293
674
|
|
294
675
|
private
|
295
676
|
|
677
|
+
# internal error record
|
678
|
+
class MapPLZException < Exception
|
679
|
+
end
|
680
|
+
|
296
681
|
# internal map object record
|
297
682
|
class GeoItem < Hash
|
298
|
-
def initialize(db =
|
683
|
+
def initialize(db = nil)
|
684
|
+
db = { type: 'array', client: nil } if db.nil?
|
299
685
|
@db = db
|
300
686
|
@db_type = db[:type]
|
301
687
|
@db_client = db[:client]
|
@@ -304,22 +690,28 @@ class MapPLZ
|
|
304
690
|
def save!
|
305
691
|
# update record in database
|
306
692
|
if @db_type == 'mongodb'
|
307
|
-
|
308
|
-
delete(:_id)
|
309
|
-
|
310
|
-
|
311
|
-
|
693
|
+
save_obj = clone
|
694
|
+
save_obj.delete(:_id)
|
695
|
+
save_obj.delete(:lat)
|
696
|
+
save_obj.delete(:lng)
|
697
|
+
save_obj.delete(:path)
|
698
|
+
save_obj.delete(:centroid)
|
699
|
+
save_obj.delete(:type)
|
700
|
+
save_obj[:geo] = JSON.parse(to_geojson)['geometry']
|
701
|
+
@db[:client].update({ _id: BSON::ObjectId(self[:_id]) }, save_obj)
|
702
|
+
elsif @db_type == 'postgis'
|
703
|
+
geojson_props = (JSON.parse(to_geojson)['properties'] || {})
|
704
|
+
@db_client.exec("UPDATE mapplz SET geom = ST_GeomFromText('#{to_wkt}'), properties = '#{geojson_props.to_json}' WHERE id = #{self[:id]}") if @db_type == 'postgis'
|
705
|
+
elsif @db_type == 'spatialite'
|
312
706
|
updaters = []
|
313
707
|
keys.each do |key|
|
314
|
-
next if [:id, :lat, :lng, :path, :type].include?(key)
|
708
|
+
next if [:id, :lat, :lng, :path, :type, :centroid].include?(key)
|
315
709
|
updaters << "#{key} = '#{self[key]}'" if self[key].is_a?(String)
|
316
710
|
updaters << "#{key} = #{self[key]}" if self[key].is_a?(Integer) || self[key].is_a?(Float)
|
317
711
|
end
|
318
|
-
updaters << "geom =
|
319
|
-
updaters << "geom = AsText('#{to_wkt}')" if @db_type == 'spatialite'
|
712
|
+
updaters << "geom = AsText('#{to_wkt}')"
|
320
713
|
if updaters.length > 0
|
321
|
-
@db_client.
|
322
|
-
@db_client.execute("UPDATE mapplz SET #{updaters.join(', ')} WHERE id = #{self[:id]}") if @db_type == 'spatialite'
|
714
|
+
@db_client.execute("UPDATE mapplz SET #{updaters.join(', ')} WHERE id = #{self[:id]}")
|
323
715
|
end
|
324
716
|
end
|
325
717
|
end
|
@@ -360,11 +752,12 @@ class MapPLZ
|
|
360
752
|
if key?(:properties)
|
361
753
|
property_list = { properties: self[:properties] }
|
362
754
|
else
|
363
|
-
property_list =
|
755
|
+
property_list = clone
|
364
756
|
property_list.delete(:lat)
|
365
757
|
property_list.delete(:lng)
|
366
758
|
property_list.delete(:path)
|
367
759
|
property_list.delete(:type)
|
760
|
+
property_list.delete(:centroid)
|
368
761
|
end
|
369
762
|
|
370
763
|
output_geo = {
|
@@ -386,13 +779,119 @@ class MapPLZ
|
|
386
779
|
}
|
387
780
|
elsif self[:type] == 'polygon'
|
388
781
|
# polygon
|
782
|
+
rings = self[:path].clone
|
783
|
+
rings.map! do |ring|
|
784
|
+
MapPLZ.flip_path(ring)
|
785
|
+
end
|
389
786
|
output_geo[:geometry] = {
|
390
787
|
type: 'Polygon',
|
391
|
-
coordinates:
|
788
|
+
coordinates: rings
|
392
789
|
}
|
393
790
|
end
|
394
791
|
output_geo.to_json
|
395
792
|
end
|
793
|
+
|
794
|
+
def centroid
|
795
|
+
# verify up-to-date centroid exists
|
796
|
+
path_hash = Digest::SHA256.digest(self[:path].to_s)
|
797
|
+
(key?(:path) && key?(:centroid) && self[:centroid] == path_hash)
|
798
|
+
end
|
799
|
+
|
800
|
+
def distance_from(user_geo)
|
801
|
+
GeoItem.centroid(self)
|
802
|
+
user_geo = MapPLZ.standardize_geo(user_geo)[0]
|
803
|
+
GeoItem.centroid(user_geo)
|
804
|
+
|
805
|
+
Geokdtree::Tree.distance([user_geo[:lat], user_geo[:lng]], [self[:lat], self[:lng]])
|
806
|
+
end
|
807
|
+
|
808
|
+
def inside?(user_geo)
|
809
|
+
GeoItem.centroid(self)
|
810
|
+
|
811
|
+
# accept [point1, point2, point3, point1] as a polygon search area
|
812
|
+
if user_geo.is_a?(Array) && user_geo[0].is_a?(Array) && !user_geo[0][0].is_a?(Array)
|
813
|
+
user_geo = [user_geo]
|
814
|
+
end
|
815
|
+
|
816
|
+
user_geo = MapPLZ.standardize_geo(user_geo)[0]
|
817
|
+
path_pts = user_geo[:path]
|
818
|
+
path_pts = path_pts[0] if user_geo[:type] == 'polygon'
|
819
|
+
|
820
|
+
# point in polygon from http://jakescruggs.blogspot.com/2009/07/point-inside-polygon-in-ruby.html
|
821
|
+
c = false
|
822
|
+
i = -1
|
823
|
+
j = path_pts.size - 1
|
824
|
+
while (i += 1) < path_pts.size
|
825
|
+
if (path_pts[i][0] <= self[:lat] && self[:lat] < path_pts[j][0]) || (path_pts[j][0] <= self[:lat] && self[:lat] < path_pts[i][0])
|
826
|
+
if self[:lng] < (path_pts[j][1] - path_pts[i][1]) * (self[:lat] - path_pts[i][0]) / (path_pts[j][0] - path_pts[i][0]) + path_pts[i][1]
|
827
|
+
c = !c
|
828
|
+
end
|
829
|
+
end
|
830
|
+
j = i
|
831
|
+
end
|
832
|
+
c
|
833
|
+
end
|
834
|
+
|
835
|
+
def self.centroid(geo_item)
|
836
|
+
# generate centroid if possible and not already existing
|
837
|
+
if geo_item.key?(:path) && !geo_item.centroid
|
838
|
+
coordinates = geo_item[:path].clone
|
839
|
+
|
840
|
+
# centroid calculation from https://code.google.com/p/tokland/source/browse/trunk/centroid
|
841
|
+
consecutive_pairs = (coordinates + [coordinates.first]).each_cons(2)
|
842
|
+
area = 0.5 * consecutive_pairs.map do |(x0, y0), (x1, y1)|
|
843
|
+
(x0 * y1) - (x1 * y0)
|
844
|
+
end
|
845
|
+
area.inject(:+)
|
846
|
+
|
847
|
+
consecutive_pairs.map! do |(x0, y0), (x1, y1)|
|
848
|
+
cross = (x0 * y1 - x1 * y0)
|
849
|
+
[(x0 + x1) * cross, (y0 + y1) * cross]
|
850
|
+
end
|
851
|
+
(center_lat, center_lng) = consecutive_pairs.transpose.map do |cs|
|
852
|
+
cs.inject(:+) / (6 * area)
|
853
|
+
end
|
854
|
+
|
855
|
+
geo_item[:centroid] = Digest::SHA256.digest(geo_item[:path].to_s)
|
856
|
+
geo_item[:lat] = center_lat
|
857
|
+
geo_item[:lng] = center_lng
|
858
|
+
end
|
859
|
+
end
|
860
|
+
end
|
861
|
+
|
862
|
+
def read_cursor(cursor)
|
863
|
+
if cursor.nil?
|
864
|
+
[]
|
865
|
+
else
|
866
|
+
geo_results = []
|
867
|
+
cursor.each do |geo_result|
|
868
|
+
if @db_type == 'postgis'
|
869
|
+
geo_item = GeoItem.new(@db)
|
870
|
+
geom = (geo_result['geo'] || geo_result[:geo]).upcase
|
871
|
+
geo_item = MapPLZ.parse_wkt(geo_item, geom)
|
872
|
+
geo_result = JSON.parse(geo_result['properties'])
|
873
|
+
elsif @db_type == 'spatialite'
|
874
|
+
geo_item = GeoItem.new(@db)
|
875
|
+
geom = (geo_result['geo'] || geo_result[:geo]).upcase
|
876
|
+
geo_item = MapPLZ.parse_wkt(geo_item, geom)
|
877
|
+
elsif @db_type == 'mongodb'
|
878
|
+
geom = { 'type' => 'Feature', 'geometry' => geo_result['geo'] }
|
879
|
+
geo_item = MapPLZ.standardize_geo(geom, true, @db)[0]
|
880
|
+
end
|
881
|
+
|
882
|
+
if geo_result.is_a?(Array)
|
883
|
+
geo_item[:properties] = geo_result
|
884
|
+
else
|
885
|
+
geo_result.keys.each do |key|
|
886
|
+
next if [:geo].include?(key.to_sym)
|
887
|
+
geo_item[key.to_sym] = geo_result[key]
|
888
|
+
end
|
889
|
+
end
|
890
|
+
|
891
|
+
geo_results << geo_item
|
892
|
+
end
|
893
|
+
geo_results
|
894
|
+
end
|
396
895
|
end
|
397
896
|
|
398
897
|
def code_line(index)
|
@@ -515,241 +1014,6 @@ class MapPLZ
|
|
515
1014
|
code_line(index + 1)
|
516
1015
|
end
|
517
1016
|
|
518
|
-
def standardize_geo(user_geo, lonlat = false)
|
519
|
-
geo_objects = []
|
520
|
-
|
521
|
-
if user_geo.is_a?(String)
|
522
|
-
begin
|
523
|
-
user_geo = JSON.parse(user_geo)
|
524
|
-
rescue
|
525
|
-
# not JSON string - attempt mapplz parse
|
526
|
-
return code(user_geo)
|
527
|
-
end
|
528
|
-
end
|
529
|
-
|
530
|
-
if user_geo.is_a?(Array) && user_geo.length > 0
|
531
|
-
if user_geo[0].is_a?(Array) && user_geo[0].length > 0
|
532
|
-
if user_geo[0][0].is_a?(Array) || (user_geo[0][0].is_a?(Hash) && user_geo[0][0].key?(:lat) && user_geo[0][0].key?(:lng))
|
533
|
-
# lines and shapes
|
534
|
-
user_geo.map! do |path|
|
535
|
-
path_pts = []
|
536
|
-
path.each do |path_pt|
|
537
|
-
if lonlat
|
538
|
-
lat = path_pt[1] || path_pt[:lat]
|
539
|
-
lng = path_pt[0] || path_pt[:lng]
|
540
|
-
else
|
541
|
-
lat = path_pt[0] || path_pt[:lat]
|
542
|
-
lng = path_pt[1] || path_pt[:lng]
|
543
|
-
end
|
544
|
-
path_pts << [lat, lng]
|
545
|
-
end
|
546
|
-
|
547
|
-
# polygon border repeats first point
|
548
|
-
if path_pts[0] == path_pts.last
|
549
|
-
geo_type = 'polygon'
|
550
|
-
else
|
551
|
-
geo_type = 'polyline'
|
552
|
-
end
|
553
|
-
|
554
|
-
geoitem = GeoItem.new(@db)
|
555
|
-
geoitem[:path] = path_pts
|
556
|
-
geoitem[:type] = geo_type
|
557
|
-
geoitem
|
558
|
-
end
|
559
|
-
return user_geo
|
560
|
-
end
|
561
|
-
end
|
562
|
-
|
563
|
-
# multiple objects being added? iterate through
|
564
|
-
if user_geo[0].is_a?(Hash) || user_geo[0].is_a?(Array)
|
565
|
-
user_geo.each do |geo_piece|
|
566
|
-
geo_objects += standardize_geo(geo_piece)
|
567
|
-
end
|
568
|
-
return geo_objects
|
569
|
-
end
|
570
|
-
|
571
|
-
# first two spots are a coordinate
|
572
|
-
validate_lat = user_geo[0].to_f != 0 || user_geo[0].to_s == '0'
|
573
|
-
validate_lng = user_geo[1].to_f != 0 || user_geo[1].to_s == '0'
|
574
|
-
|
575
|
-
if validate_lat && validate_lng
|
576
|
-
geo_object = GeoItem.new(@db)
|
577
|
-
geo_object[:type] = 'point'
|
578
|
-
|
579
|
-
if lonlat
|
580
|
-
geo_object[:lat] = user_geo[1].to_f
|
581
|
-
geo_object[:lng] = user_geo[0].to_f
|
582
|
-
else
|
583
|
-
geo_object[:lat] = user_geo[0].to_f
|
584
|
-
geo_object[:lng] = user_geo[1].to_f
|
585
|
-
end
|
586
|
-
else
|
587
|
-
fail 'no latitude or longitude found'
|
588
|
-
end
|
589
|
-
|
590
|
-
# assume user properties are an ordered array of values known to the user
|
591
|
-
user_properties = user_geo.drop(2)
|
592
|
-
|
593
|
-
# only one property and it's a hash? it's a hash of properties
|
594
|
-
if user_properties.length == 1 && user_properties[0].is_a?(Hash)
|
595
|
-
user_properties[0].keys.each do |key|
|
596
|
-
geo_object[key.to_sym] = user_properties[0][key]
|
597
|
-
end
|
598
|
-
else
|
599
|
-
geo_object[:properties] = user_properties
|
600
|
-
end
|
601
|
-
|
602
|
-
geo_objects << geo_object
|
603
|
-
|
604
|
-
elsif user_geo.is_a?(Hash)
|
605
|
-
# check for lat and lng
|
606
|
-
validate_lat = false
|
607
|
-
validate_lat = 'lat' if user_geo.key?('lat') || user_geo.key?(:lat)
|
608
|
-
validate_lat ||= 'latitude' if user_geo.key?('latitude') || user_geo.key?(:latitude)
|
609
|
-
|
610
|
-
validate_lng = false
|
611
|
-
validate_lng = 'lng' if user_geo.key?('lng') || user_geo.key?(:lng)
|
612
|
-
validate_lng ||= 'lon' if user_geo.key?('lon') || user_geo.key?(:lon)
|
613
|
-
validate_lng ||= 'long' if user_geo.key?('long') || user_geo.key?(:long)
|
614
|
-
validate_lng ||= 'longitude' if user_geo.key?('longitude') || user_geo.key?(:longitude)
|
615
|
-
|
616
|
-
if validate_lat && validate_lng
|
617
|
-
# single hash
|
618
|
-
geo_object = GeoItem.new(@db)
|
619
|
-
geo_object[:lat] = user_geo[validate_lat].to_f
|
620
|
-
geo_object[:lng] = user_geo[validate_lng].to_f
|
621
|
-
geo_object[:type] = 'point'
|
622
|
-
|
623
|
-
user_geo.keys.each do |key|
|
624
|
-
next if key == validate_lat || key == validate_lng
|
625
|
-
geo_object[key.to_sym] = user_geo[key]
|
626
|
-
end
|
627
|
-
geo_objects << geo_object
|
628
|
-
elsif user_geo.key?('path') || user_geo.key?(:path)
|
629
|
-
# try line or polygon
|
630
|
-
path_pts = []
|
631
|
-
path = user_geo['path'] if user_geo.key?('path')
|
632
|
-
path = user_geo[:path] if user_geo.key?(:path)
|
633
|
-
path.each do |path_pt|
|
634
|
-
if lonlat
|
635
|
-
lat = path_pt[1] || path_pt[:lat]
|
636
|
-
lng = path_pt[0] || path_pt[:lng]
|
637
|
-
else
|
638
|
-
lat = path_pt[0] || path_pt[:lat]
|
639
|
-
lng = path_pt[1] || path_pt[:lng]
|
640
|
-
end
|
641
|
-
path_pts << [lat, lng]
|
642
|
-
end
|
643
|
-
|
644
|
-
# polygon border repeats first point
|
645
|
-
if path_pts[0] == path_pts.last
|
646
|
-
geo_type = 'polygon'
|
647
|
-
else
|
648
|
-
geo_type = 'polyline'
|
649
|
-
end
|
650
|
-
|
651
|
-
geoitem = GeoItem.new(@db)
|
652
|
-
geoitem[:path] = path_pts
|
653
|
-
geoitem[:type] = geo_type
|
654
|
-
|
655
|
-
property_list = user_geo.clone
|
656
|
-
property_list = property_list[:properties] if property_list.key?(:properties)
|
657
|
-
property_list = property_list['properties'] if property_list.key?('properties')
|
658
|
-
property_list.delete(:path)
|
659
|
-
property_list.keys.each do |prop|
|
660
|
-
geoitem[prop.to_sym] = property_list[prop]
|
661
|
-
end
|
662
|
-
|
663
|
-
geo_objects << geoitem
|
664
|
-
else
|
665
|
-
# try GeoJSON
|
666
|
-
if user_geo.key?(:type)
|
667
|
-
user_geo['type'] = user_geo[:type] || ''
|
668
|
-
user_geo['features'] = user_geo[:features] if user_geo.key?(:features)
|
669
|
-
user_geo['properties'] = user_geo[:properties] || {}
|
670
|
-
if user_geo.key?(:geometry)
|
671
|
-
user_geo['geometry'] = user_geo[:geometry]
|
672
|
-
user_geo['geometry']['type'] = user_geo[:geometry][:type]
|
673
|
-
user_geo['geometry']['coordinates'] = user_geo[:geometry][:coordinates]
|
674
|
-
end
|
675
|
-
end
|
676
|
-
if user_geo.key?('type')
|
677
|
-
if user_geo['type'] == 'FeatureCollection' && user_geo.key?('features')
|
678
|
-
# recursive onto features
|
679
|
-
user_geo['features'].each do |feature|
|
680
|
-
geo_objects += standardize_geo(feature)
|
681
|
-
end
|
682
|
-
elsif user_geo.key?('geometry') && user_geo['geometry'].key?('coordinates')
|
683
|
-
# each feature
|
684
|
-
coordinates = user_geo['geometry']['coordinates']
|
685
|
-
|
686
|
-
if user_geo['geometry']['type'] == 'Point'
|
687
|
-
geo_object = GeoItem.new(@db)
|
688
|
-
geo_object[:lat] = coordinates[1].to_f
|
689
|
-
geo_object[:lng] = coordinates[0].to_f
|
690
|
-
geo_object[:type] = 'point'
|
691
|
-
geo_objects << geo_object
|
692
|
-
elsif user_geo['geometry']['type'] == 'LineString'
|
693
|
-
geo_object = GeoItem.new(@db)
|
694
|
-
MapPLZ.flip_path(coordinates)
|
695
|
-
geo_object[:path] = coordinates
|
696
|
-
geo_object[:type] = 'polyline'
|
697
|
-
geo_objects << geo_object
|
698
|
-
elsif user_geo['geometry']['type'] == 'Polygon'
|
699
|
-
geo_object = GeoItem.new(@db)
|
700
|
-
coordinates.map! do |ring|
|
701
|
-
MapPLZ.flip_path(ring)
|
702
|
-
end
|
703
|
-
geo_object[:path] = coordinates
|
704
|
-
geo_object[:type] = 'polygon'
|
705
|
-
geo_objects << geo_object
|
706
|
-
elsif user_geo['geometry']['type'] == 'MultiPoint'
|
707
|
-
coordinates.each do |point|
|
708
|
-
geo_object = GeoItem.new(@db)
|
709
|
-
geo_object[:lat] = point[1].to_f
|
710
|
-
geo_object[:lng] = point[0].to_f
|
711
|
-
geo_object[:type] = 'point'
|
712
|
-
geo_objects << geo_object
|
713
|
-
end
|
714
|
-
elsif user_geo['geometry']['type'] == 'MultiLineString'
|
715
|
-
coordinates.each do |line|
|
716
|
-
geo_object = GeoItem.new(@db)
|
717
|
-
geo_object[:path] = MapPLZ.flip_path(line)
|
718
|
-
geo_object[:type] = 'polyline'
|
719
|
-
geo_objects << geo_object
|
720
|
-
end
|
721
|
-
elsif user_geo['geometry']['type'] == 'MultiPolygon'
|
722
|
-
coordinates.each do |poly|
|
723
|
-
geo_object = GeoItem.new(@db)
|
724
|
-
poly.map! do |ring|
|
725
|
-
MapPLZ.flip_path(ring)
|
726
|
-
end
|
727
|
-
geo_object[:path] = poly
|
728
|
-
geo_object[:type] = 'polygon'
|
729
|
-
geo_objects << geo_object
|
730
|
-
end
|
731
|
-
end
|
732
|
-
|
733
|
-
# store properties on all generated geometries
|
734
|
-
prop_keys = {}
|
735
|
-
if user_geo.key?('properties')
|
736
|
-
user_geo['properties'].keys.each do |key|
|
737
|
-
prop_keys[key.to_sym] = user_geo['properties'][key]
|
738
|
-
end
|
739
|
-
end
|
740
|
-
geo_objects.each do |geo|
|
741
|
-
prop_keys.keys.each do |key|
|
742
|
-
geo[key] = prop_keys[key]
|
743
|
-
end
|
744
|
-
end
|
745
|
-
end
|
746
|
-
end
|
747
|
-
end
|
748
|
-
end
|
749
|
-
|
750
|
-
geo_objects
|
751
|
-
end
|
752
|
-
|
753
1017
|
def parse_sql(where_clause, add_on = nil)
|
754
1018
|
where_clause.downcase! unless where_clause.blank?
|
755
1019
|
where_clause = where_clause.gsub('?', '\'?\'') if add_on.present?
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mapplz
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Doiron
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-07-
|
11
|
+
date: 2014-07-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|