mapplz 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +50 -21
  3. data/lib/mapplz.rb +255 -35
  4. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7034405767e5ef4ee3c72c162821971aaa06b27c
4
- data.tar.gz: 3cb3eec174363117097f84b9ea8ed093ffb49255
3
+ metadata.gz: 9e48a7476c329db17d5f066edd761a95aa5c1911
4
+ data.tar.gz: fa02fbea3993691d2951d7ea22c320b82f7d8fe3
5
5
  SHA512:
6
- metadata.gz: 19aa227e8159c73ec9fbc3c63519b2613fd0829aecae4a891d48df93309bd0e9a042b44177934713e86eddf6fdd7d4c418afb059e7672e69769c1fe95e111663
7
- data.tar.gz: 443b860f75737c92837f1f9ef4a1d5121670470f002229f884e1e9be337dcb319966aeb3c5c4b4a8947324d7770966635c8d670ec72460f7c5260ca309d3b076
6
+ metadata.gz: d7de31bce69a2215c887d0ddb3ee0a0a5523853108b92a51dd57f63a6eab89ace3f97fc5f95cc393024541869540c0517133058be29655a7dd5900e9e1896e65
7
+ data.tar.gz: 196c08bb5e6ae90457f206f4110b8260a6d032efa1fe4f9f49063c10d597b495b47062b896f1810a1c94ebadd87e324e80ad74927a64c7fa52c928f13fc5b5b7
data/README.md CHANGED
@@ -19,6 +19,8 @@ mapstore << [point1, point2]
19
19
 
20
20
  # a line or shape
21
21
  mapstore << [[point1, point2, point3]]
22
+ mapstore << [[point1, point2, point3, point1]]
23
+ mapstore << { path: [point1, point2], label: 'hello world' }
22
24
 
23
25
  # GeoJSON string or object
24
26
  mapstore << { type: "Feature", geometry: { type: "Point", coordinates: [lng, lat] } }
@@ -41,7 +43,7 @@ mapstore << { type: "Feature", geometry: { type: "Point", properties: { name: "B
41
43
 
42
44
  ## Export HTML and GeoJSON
43
45
 
44
- Currently you can output the data as GeoJSON:
46
+ You can output the data anytime as GeoJSON:
45
47
 
46
48
  ```
47
49
  @mapper = MapPLZ.new
@@ -49,24 +51,44 @@ Currently you can output the data as GeoJSON:
49
51
  @mapper.to_geojson
50
52
  ```
51
53
 
52
- You will be able to output an interactive, HTML+JavaScript map with Leaflet.js
54
+ You can add interactive, HTML+JavaScript maps which use Leaflet.js
53
55
 
54
56
  ```
55
57
  require 'mapplz'
56
58
 
57
59
  @mapper = MapPLZ.new
58
- @mapper << mapplz_code
60
+ @mapper << geo_stuff
59
61
  @mapper.render_html
60
62
  ```
61
63
 
62
- You would be able to use it in Rails + HAML templates, too:
64
+ This is based on the Leaflet-Rails plugin. Set Leaflet defaults directly:
63
65
 
64
66
  ```
65
- div#map
66
- = @mapper.embed_html
67
+ Leaflet.tile_layer = 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}.png'
68
+ @mapper.render_html
69
+ ```
70
+
71
+ You can pass options to render_html, including new default styles for lines and shapes:
72
+
73
+ ```
74
+ @mapper.render_html(max_zoom: 18, fillColor: '#00f')
67
75
  ```
68
76
 
69
- ## MapPLZ queries
77
+ You can also add styles to your data as it's entered into the map datastore.
78
+
79
+ ```
80
+ @mapper << { path: [point1, point2], color: 'red', opacity: 0.8 }
81
+ ```
82
+
83
+ All of these would appear as clickable map features with popups:
84
+
85
+ ```
86
+ @mapper << [40, -70, 'hello popup']
87
+ @mapper << { lat: 40, lng: -80, label: 'hello popup' }
88
+ @mapper << { path: [point1, point2], color: 'red', label: 'the red line' }
89
+ ```
90
+
91
+ ## Queries
70
92
 
71
93
  All of these are valid ways to query geodata:
72
94
 
@@ -79,23 +101,17 @@ mapplz.count
79
101
  mapplz.count('layer = ?', name_of_layer)
80
102
  ```
81
103
 
82
- ## MapPLZ language
83
- You can make a map super quickly by using the MapPLZ language. A MapPLZ map
84
- can be described using as simply as this:
104
+ ## Databases
85
105
 
86
- ```
87
- map
88
- marker
89
- "The Statue of Liberty"
90
- [40, -70]
91
- plz
92
- plz
93
- ```
106
+ You can store geodata in SQLite/Spatialite, Postgres/PostGIS, or MongoDB.
94
107
 
95
- ## MapPLZ and Databases
108
+ MapPLZ simplifies geodata management and queries.
96
109
 
97
- You can store geodata in SQLite/Spatialite databases, or in Postgres/PostGIS
98
- databases, using a simplified MapPLZ API.
110
+ ```
111
+ # setting the database
112
+ # if a site uses ActiveRecord, the database will be set automatically
113
+ mapplz.choose_db('postgis')
114
+ ```
99
115
 
100
116
  ```
101
117
  # working with records
@@ -114,6 +130,19 @@ mapplz.near([lat, lng])
114
130
  mapplz.inside([point1, point2, point3, point1])
115
131
  ```
116
132
 
133
+ ## Language
134
+ You can make a map super quickly by using the MapPLZ language. A MapPLZ map
135
+ can be described using as simply as this:
136
+
137
+ ```
138
+ map
139
+ marker
140
+ "The Statue of Liberty"
141
+ [40, -70]
142
+ plz
143
+ plz
144
+ ```
145
+
117
146
  ## License
118
147
 
119
148
  Free BSD License
data/lib/mapplz.rb CHANGED
@@ -2,17 +2,30 @@
2
2
 
3
3
  require 'sql_parser'
4
4
  require 'json'
5
+ include Leaflet::ViewHelpers
5
6
 
6
7
  # MapPLZ datastore
7
8
  class MapPLZ
9
+ DATABASES = %w(array postgres postgresql postgis sqlite spatialite mongodb)
10
+
8
11
  def initialize
9
12
  @db_type = 'array'
10
13
  @parser = SqlParser.new
11
14
  @my_array = []
15
+
16
+ choose_db(ActiveRecord::Base.connection.adapter_name) if defined?(ActiveRecord)
12
17
  end
13
18
 
14
- def add(user_geo)
15
- geo_objects = standardize_geo(user_geo)
19
+ def choose_db(db)
20
+ db.downcase!
21
+ fail 'Database type not supported by MapPLZ' unless DATABASES.include?(db)
22
+ db = 'postgis' if db == 'postgres' || db == 'postgresql'
23
+ db = 'spatialite' if db == 'sqlite'
24
+ @db_type = db
25
+ end
26
+
27
+ def add(user_geo, lonlat = false)
28
+ geo_objects = standardize_geo(user_geo, lonlat)
16
29
  @my_array += geo_objects
17
30
 
18
31
  if geo_objects.length == 1
@@ -31,7 +44,7 @@ class MapPLZ
31
44
  end
32
45
  end
33
46
 
34
- def query(where_clause, add_on)
47
+ def query(where_clause, add_on = nil)
35
48
  if where_clause.present?
36
49
  if @db_type == 'array'
37
50
  query_array(where_clause, add_on)
@@ -71,6 +84,87 @@ class MapPLZ
71
84
  geojson.to_json
72
85
  end
73
86
 
87
+ def render_html(options = {})
88
+ # Leaflet options
89
+ options[:tile_layer] ||= Leaflet.tile_layer
90
+ options[:attribution] ||= Leaflet.attribution
91
+ options[:max_zoom] ||= Leaflet.max_zoom
92
+ options[:container_id] ||= 'map'
93
+
94
+ geojson_features = JSON.parse(to_geojson)['features']
95
+
96
+ # use Leaflet to add clickable markers
97
+ options[:markers] = []
98
+ geojson_features.each do |feature|
99
+ next if feature['geometry']['type'] != 'Point'
100
+
101
+ if feature.key?('properties')
102
+ if feature['properties'].is_a?(Hash) && feature['properties'].key?('label')
103
+ label = feature['properties']['label']
104
+ elsif feature['properties'].key?('properties') && feature['properties']['properties'].is_a?(Array) && feature['properties']['properties'].length == 1
105
+ label = feature['properties']['properties'][0]
106
+ end
107
+ else
108
+ label = nil
109
+ end
110
+ options[:markers] << { latlng: feature['geometry']['coordinates'].reverse, popup: label }
111
+ end
112
+
113
+ render_text = map(options)
114
+
115
+ # add clickable lines and polygons after
116
+ # Leaflet-Rails does not support clickable lines or any polygons
117
+ geojson_features.each do |feature|
118
+ next if feature['geometry']['type'] == 'Point'
119
+
120
+ label = nil
121
+ path_options = {}
122
+ path_options[:color] = options[:color] if options.key?(:color)
123
+ path_options[:opacity] = options[:opacity] if options.key?(:opacity)
124
+ path_options[:fillColor] = options[:fillColor] if options.key?(:fillColor)
125
+ path_options[:fillOpacity] = options[:fillOpacity] if options.key?(:fillOpacity)
126
+ path_options[:weight] = options[:weight] if options.key?(:weight)
127
+ path_options[:stroke] = options[:stroke] if options.key?(:stroke)
128
+
129
+ if feature.key?('properties')
130
+ if feature['properties'].key?('label')
131
+ label = feature['properties']['label']
132
+ elsif feature['properties'].key?('properties') && feature['properties']['properties'].is_a?(Array) && feature['properties']['properties'].length == 1
133
+ label = feature['properties']['properties'][0]
134
+ end
135
+
136
+ path_options[:color] = feature['properties']['color'] if feature['properties'].key?('color')
137
+ path_options[:opacity] = feature['properties']['opacity'].to_f if feature['properties'].key?('opacity')
138
+ path_options[:fillColor] = feature['properties']['fillColor'] if feature['properties'].key?('fillColor')
139
+ path_options[:fillOpacity] = feature['properties']['fillOpacity'].to_f if feature['properties'].key?('fillOpacity')
140
+ path_options[:weight] = feature['properties']['weight'].to_i if feature['properties'].key?('weight')
141
+ path_options[:stroke] = feature['properties']['stroke'] if feature['properties'].key?('stroke')
142
+ path_options[:clickable] = true unless label.nil?
143
+ end
144
+
145
+ flip_coordinates = feature['geometry']['coordinates']
146
+ if flip_coordinates[0][0].is_a?(Array)
147
+ flip_coordinates.each do |segment|
148
+ segment.map! { |coord| coord.reverse }
149
+ end
150
+ else
151
+ flip_coordinates.map! { |coord| coord.reverse }
152
+ end
153
+
154
+ if feature['geometry']['type'] == 'Polyline'
155
+ render_text += ('line = L.polyline(' + flip_coordinates.to_json + ", #{path_options.to_json}).addTo(map);\n").html_safe
156
+ render_text += "line.bindPopup('#{label}');\n".html_safe unless label.nil?
157
+ elsif feature['geometry']['type'] == 'Polygon'
158
+ render_text += ('polygon = L.polygon(' + flip_coordinates[0].to_json + ", #{path_options.to_json}).addTo(map);\n").html_safe
159
+ render_text += "polygon.bindPopup('#{label}');\n".html_safe unless label.nil?
160
+ end
161
+
162
+ render_text
163
+ end
164
+
165
+ render_text
166
+ end
167
+
74
168
  # alias methods
75
169
 
76
170
  # aliases for add
@@ -82,6 +176,11 @@ class MapPLZ
82
176
  add(user_geo)
83
177
  end
84
178
 
179
+ # aliases for query
180
+ def where(where_clause, add_on = nil)
181
+ query(where_clause, add_on)
182
+ end
183
+
85
184
  # aliases for count
86
185
  def size(where_clause = nil, add_on = nil)
87
186
  count(where_clause, add_on)
@@ -91,8 +190,36 @@ class MapPLZ
91
190
  count(where_clause, add_on)
92
191
  end
93
192
 
193
+ # aliases for render_html
194
+ def embed_html(options = {})
195
+ render_html(options)
196
+ end
197
+
94
198
  private
95
199
 
200
+ # internal map object record
201
+ class GeoItem < Hash
202
+ def initialize(db)
203
+ @db_type = db
204
+ end
205
+
206
+ def save!
207
+ # update record in database
208
+ unless @db_type == 'array'
209
+ end
210
+ end
211
+
212
+ def delete_item
213
+ if @db_type == 'array'
214
+ keys.each do |key|
215
+ delete(key)
216
+ end
217
+ else
218
+ # update record in database
219
+ end
220
+ end
221
+ end
222
+
96
223
  def code_line(index)
97
224
  return if index >= @code_lines.length
98
225
  line = @code_lines[index].strip
@@ -139,24 +266,27 @@ class MapPLZ
139
266
  if codeline.index('plz') || codeline.index('please')
140
267
 
141
268
  if @code_level == 'marker'
142
- @code_layers << {
143
- lat: @code_latlngs[0][0],
144
- lng: @code_latlngs[0][1],
145
- label: @code_label || ''
146
- }
269
+ geoitem = GeoItem.new(@db_type)
270
+ geoitem[:lat] = @code_latlngs[0][0]
271
+ geoitem[:lng] = @code_latlngs[0][1]
272
+ geoitem[:label] = @code_label || ''
273
+
274
+ @code_layers << geoitem
147
275
  elsif @code_level == 'line'
148
- @code_layers << {
149
- path: @code_latlngs,
150
- strokeColor: (@code_color || ''),
151
- label: @code_label || ''
152
- }
276
+ geoitem = GeoItem.new(@db_type)
277
+ geoitem[:path] = @code_latlngs
278
+ geoitem[:stroke_color] = (@code_color || '')
279
+ geoitem[:label] = @code_label || ''
280
+
281
+ @code_layers << geoitem
153
282
  elsif @code_level == 'shape'
154
- @code_layers << {
155
- paths: @code_latlngs,
156
- strokeColor: (@code_color || ''),
157
- fillColor: (@code_color || ''),
158
- label: @code_label || ''
159
- }
283
+ geoitem = GeoItem.new(@db_type)
284
+ geoitem[:paths] = @code_latlngs
285
+ geoitem[:stroke_color] = (@code_color || '')
286
+ geoitem[:fill_color] = (@code_color || '')
287
+ geoitem[:label] = @code_label || ''
288
+
289
+ @code_layers << geoitem
160
290
  end
161
291
 
162
292
  if @code_button
@@ -210,7 +340,7 @@ class MapPLZ
210
340
  code_line(index + 1)
211
341
  end
212
342
 
213
- def standardize_geo(user_geo)
343
+ def standardize_geo(user_geo, lonlat = false)
214
344
  geo_objects = []
215
345
 
216
346
  if user_geo.is_a?(String)
@@ -223,6 +353,38 @@ class MapPLZ
223
353
  end
224
354
 
225
355
  if user_geo.is_a?(Array) && user_geo.length > 0
356
+ if user_geo[0].is_a?(Array) && user_geo[0].length > 0
357
+ 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))
358
+ # lines and shapes
359
+ user_geo.map! do |path|
360
+ path_pts = []
361
+ path.each do |path_pt|
362
+ if lonlat
363
+ lat = path_pt[1] || path_pt[:lat]
364
+ lng = path_pt[0] || path_pt[:lng]
365
+ else
366
+ lat = path_pt[0] || path_pt[:lat]
367
+ lng = path_pt[1] || path_pt[:lng]
368
+ end
369
+ path_pts << [lat, lng]
370
+ end
371
+
372
+ # polygon border repeats first point
373
+ if path_pts[0] == path_pts.last
374
+ geo_type = 'polygon'
375
+ else
376
+ geo_type = 'polyline'
377
+ end
378
+
379
+ geoitem = GeoItem.new(@db_type)
380
+ geoitem[:path] = path_pts
381
+ geoitem[:type] = geo_type
382
+ geoitem
383
+ end
384
+ return user_geo
385
+ end
386
+ end
387
+
226
388
  # multiple objects being added? iterate through
227
389
  if user_geo[0].is_a?(Hash) || user_geo[0].is_a?(Array)
228
390
  user_geo.each do |geo_piece|
@@ -236,10 +398,16 @@ class MapPLZ
236
398
  validate_lng = user_geo[1].to_f != 0 || user_geo[1].to_s == '0'
237
399
 
238
400
  if validate_lat && validate_lng
239
- geo_object = {
240
- lat: user_geo[0].to_f,
241
- lng: user_geo[1].to_f
242
- }
401
+ geo_object = GeoItem.new(@db_type)
402
+ geo_object[:type] = 'point'
403
+
404
+ if lonlat
405
+ geo_object[:lat] = user_geo[1].to_f
406
+ geo_object[:lng] = user_geo[0].to_f
407
+ else
408
+ geo_object[:lat] = user_geo[0].to_f
409
+ geo_object[:lng] = user_geo[1].to_f
410
+ end
243
411
  else
244
412
  fail 'no latitude or longitude found'
245
413
  end
@@ -272,15 +440,52 @@ class MapPLZ
272
440
 
273
441
  if validate_lat && validate_lng
274
442
  # single hash
275
- geo_object = {
276
- lat: user_geo[validate_lat].to_f,
277
- lng: user_geo[validate_lng].to_f
278
- }
443
+ geo_object = GeoItem.new(@db_type)
444
+ geo_object[:lat] = user_geo[validate_lat].to_f
445
+ geo_object[:lng] = user_geo[validate_lng].to_f
446
+ geo_object[:type] = 'point'
447
+
279
448
  user_geo.keys.each do |key|
280
449
  next if key == validate_lat || key == validate_lng
281
450
  geo_object[key.to_sym] = user_geo[key]
282
451
  end
283
452
  geo_objects << geo_object
453
+ elsif user_geo.key?('path') || user_geo.key?(:path)
454
+ # try line or polygon
455
+ path_pts = []
456
+ path = user_geo['path'] if user_geo.key?('path')
457
+ path = user_geo[:path] if user_geo.key?(:path)
458
+ path.each do |path_pt|
459
+ if lonlat
460
+ lat = path_pt[1] || path_pt[:lat]
461
+ lng = path_pt[0] || path_pt[:lng]
462
+ else
463
+ lat = path_pt[0] || path_pt[:lat]
464
+ lng = path_pt[1] || path_pt[:lng]
465
+ end
466
+ path_pts << [lat, lng]
467
+ end
468
+
469
+ # polygon border repeats first point
470
+ if path_pts[0] == path_pts.last
471
+ geo_type = 'polygon'
472
+ else
473
+ geo_type = 'polyline'
474
+ end
475
+
476
+ geoitem = GeoItem.new(@db_type)
477
+ geoitem[:path] = path_pts
478
+ geoitem[:type] = geo_type
479
+
480
+ property_list = user_geo.clone
481
+ property_list = property_list[:properties] if property_list.key?(:properties)
482
+ property_list = property_list['properties'] if property_list.key?('properties')
483
+ property_list.delete(:path)
484
+ property_list.keys.each do |prop|
485
+ geoitem[prop.to_sym] = property_list[prop]
486
+ end
487
+
488
+ geo_objects << geoitem
284
489
  else
285
490
  # try GeoJSON
286
491
  if user_geo.key?('type')
@@ -291,7 +496,7 @@ class MapPLZ
291
496
  end
292
497
  elsif user_geo.key?('geometry') && user_geo['geometry'].key?('coordinates')
293
498
  # individual feature
294
- geo_object = {}
499
+ geo_object = GeoItem.new(@db_type)
295
500
  coordinates = user_geo['geometry']['coordinates']
296
501
  if user_geo.key?('properties')
297
502
  user_geo['properties'].keys.each do |key|
@@ -302,6 +507,7 @@ class MapPLZ
302
507
  if user_geo['geometry']['type'] == 'Point'
303
508
  geo_object[:lat] = coordinates[1].to_f
304
509
  geo_object[:lng] = coordinates[0].to_f
510
+ geo_object[:type] = 'point'
305
511
  end
306
512
 
307
513
  geo_objects << geo_object
@@ -314,7 +520,7 @@ class MapPLZ
314
520
  end
315
521
  elsif user_geo.key?(:geometry) && user_geo[:geometry].key?(:coordinates)
316
522
  # individual feature
317
- geo_object = {}
523
+ geo_object = GeoItem.new(@db_type)
318
524
  coordinates = user_geo[:geometry][:coordinates]
319
525
  if user_geo.key?(:properties)
320
526
  user_geo[:properties].keys.each do |key|
@@ -325,6 +531,7 @@ class MapPLZ
325
531
  if user_geo[:geometry][:type] == 'Point'
326
532
  geo_object[:lat] = coordinates[1].to_f
327
533
  geo_object[:lng] = coordinates[0].to_f
534
+ geo_object[:type] = 'point'
328
535
  end
329
536
 
330
537
  geo_objects << geo_object
@@ -338,7 +545,7 @@ class MapPLZ
338
545
 
339
546
  def as_geojson(geo_object)
340
547
  if geo_object.key?(:properties)
341
- property_list = geo_object[:properties]
548
+ property_list = { properties: geo_object[:properties] }
342
549
  else
343
550
  property_list = geo_object.clone
344
551
  property_list.delete(:lat)
@@ -351,22 +558,34 @@ class MapPLZ
351
558
  properties: property_list
352
559
  }
353
560
 
354
- if geo_object.key?(:lat) && geo_object.key?(:lng)
561
+ if geo_object[:type] == 'point'
355
562
  # point
356
563
  output_geo[:geometry] = {
357
564
  type: 'Point',
358
565
  coordinates: [geo_object[:lng], geo_object[:lat]]
359
566
  }
360
- else
361
- # other geometry
567
+ elsif geo_object[:type] == 'polyline'
568
+ # line
362
569
  output_geo[:geometry] = {
363
570
  type: 'Polyline',
364
- coordinates: [geo_object[:path]]
571
+ coordinates: flip_path(geo_object[:path])
572
+ }
573
+ elsif geo_object[:type] == 'polygon'
574
+ # polygon
575
+ output_geo[:geometry] = {
576
+ type: 'Polygon',
577
+ coordinates: [flip_path(geo_object[:path])]
365
578
  }
366
579
  end
367
580
  output_geo
368
581
  end
369
582
 
583
+ def flip_path(path)
584
+ path.map! do |pt|
585
+ pt.reverse
586
+ end
587
+ end
588
+
370
589
  def query_array(where_clause, add_on = nil)
371
590
  # prepare where clause for parse
372
591
  where_clause.downcase! unless where_clause.blank?
@@ -378,6 +597,7 @@ class MapPLZ
378
597
 
379
598
  # filter array
380
599
  @my_array.select do |geo_obj|
600
+ return false if geo_obj.nil?
381
601
  is_valid = true
382
602
  conditions.each do |condition|
383
603
  field = condition[:field]
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.1
4
+ version: 0.1.2
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-03 00:00:00.000000000 Z
11
+ date: 2014-07-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec