mapplz 0.1.1 → 0.1.2

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.
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