mapplz 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|