mapkick-rb 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 142c0d5f613b1bf31098593e360bcef22fc2f67f8bec3df00eae695ffc38374a
4
- data.tar.gz: 5f9411788846f23203395f615448736c12712f4d71b0069306c0b053731e4ba7
3
+ metadata.gz: 6040b675cb3aebd2b20f20fb679d6dbd8da27282bfca83a2a3063ed8ebf9a593
4
+ data.tar.gz: 0b1de6cbd644d9d9f5eb52ec854403c47218fc2198e00a23d5d435420b2b68c4
5
5
  SHA512:
6
- metadata.gz: 3b40922653a27f565fa349d4d1687d04b2add2aacf26bcab042f6ef9a87168940e3dafe7cf39e058f93b430391fbdb484779b3fce8eec3ae4d43678d97b5fa30
7
- data.tar.gz: 17a189a1a596dfa60889ba6c8c2595faeebdeeecc4235d74adc3ebba3c8a774fcbd1347b7ce0d5c579ebb7dc363a8186a0d3763eaf2f83be3fd0ce2dd7ff9a2b
6
+ metadata.gz: f312d4524c8705ed35a2f4d6d51d3cc1651156e93ac335c22d74ae5a143687303a3062b8877c92008836c3db50fdfa62c59238cddf2448a82448f5565e73399f
7
+ data.tar.gz: da747aaa4b2bb078f981d5de52ba4c61fa9070c46e8ce6ff2364be7ec2c188077d717b31d1e69063ecbdafc2b26d4dbfb4094165a3ceba91739e8b3bdaecb588
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 0.1.2 (2023-02-08)
2
+
3
+ - Updated Mapkick.js to 0.2.3
4
+ - Added experimental support for area maps
5
+
1
6
  ## 0.1.1 (2023-01-24)
2
7
 
3
8
  - Updated Mapkick.js to 0.2.2
data/README.md CHANGED
@@ -16,7 +16,7 @@ Add this line to your application’s Gemfile:
16
16
  gem "mapkick-rb"
17
17
  ```
18
18
 
19
- Mapkick uses [Mapbox GL JS v1](https://github.com/mapbox/mapbox-gl-js/tree/v1.13.3). To use tiles from Mapbox, [create a Mapbox account](https://account.mapbox.com/auth/signup/) to get an access token and set `ENV["MAPBOX_ACCESS_TOKEN"]` in your environment.
19
+ Mapkick uses [Mapbox GL JS v1](https://github.com/mapbox/mapbox-gl-js/tree/v1.13.3). To use tiles from Mapbox, [create a Mapbox account](https://account.mapbox.com/auth/signup/) to get an access token and set `ENV["MAPBOX_ACCESS_TOKEN"]` in your environment (or set `Mapkick.options[:access_token]` in an initializer).
20
20
 
21
21
  Then follow the instructions for your JavaScript setup:
22
22
 
@@ -79,12 +79,18 @@ In `app/assets/javascripts/application.js`, add:
79
79
 
80
80
  ## Maps
81
81
 
82
- Create a map
82
+ Point map
83
83
 
84
84
  ```erb
85
85
  <%= js_map [{latitude: 37.7829, longitude: -122.4190}] %>
86
86
  ```
87
87
 
88
+ Area map (experimental)
89
+
90
+ ```erb
91
+ <%= area_map [{geometry: {type: "Polygon", coordinates: ...}}] %>
92
+ ```
93
+
88
94
  ## Data
89
95
 
90
96
  Data can be an array
@@ -99,11 +105,13 @@ Or a URL that returns JSON (same format as above)
99
105
  <%= js_map cities_path %>
100
106
  ```
101
107
 
102
- You can use `latitude`, `lat`, `longitude`, `lon`, and `lng`
108
+ ### Point Map
109
+
110
+ Use `latitude` or `lat` for latitude and `longitude`, `lon`, or `lng` for longitude
103
111
 
104
112
  You can specify a label and tooltip for each data point
105
113
 
106
- ```javascript
114
+ ```ruby
107
115
  {
108
116
  latitude: ...,
109
117
  longitude: ...,
@@ -112,6 +120,20 @@ You can specify a label and tooltip for each data point
112
120
  }
113
121
  ```
114
122
 
123
+ ### Area Map
124
+
125
+ Use `geometry` with a GeoJSON `Polygon` or `MultiPolygon`
126
+
127
+ You can specify a label and tooltip for each data point
128
+
129
+ ```ruby
130
+ {
131
+ geometry: {type: "Polygon", coordinates: ...},
132
+ label: "Hot Chicken Takeover",
133
+ tooltip: "5 stars"
134
+ }
135
+ ```
136
+
115
137
  ## Options
116
138
 
117
139
  Id, width, and height
@@ -120,16 +142,22 @@ Id, width, and height
120
142
  <%= js_map data, id: "cities-map", width: "800px", height: "500px" %>
121
143
  ```
122
144
 
123
- Markers
145
+ Marker color
124
146
 
125
147
  ```erb
126
148
  <%= js_map data, markers: {color: "#f84d4d"} %>
127
149
  ```
128
150
 
129
- Tooltips
151
+ Show tooltips on click instead of hover
152
+
153
+ ```erb
154
+ <%= js_map data, tooltips: {hover: false} %>
155
+ ```
156
+
157
+ Allow HTML in tooltips (must sanitize manually)
130
158
 
131
159
  ```erb
132
- <%= js_map data, tooltips: {hover: false, html: true} %>
160
+ <%= js_map data, tooltips: {html: true} %>
133
161
  ```
134
162
 
135
163
  Map style
@@ -1,7 +1,17 @@
1
1
  module Mapkick
2
2
  module Helper
3
- # don't break out options since need to merge with default options
4
3
  def js_map(data_source, **options)
4
+ mapkick_map "Map", data_source, **options
5
+ end
6
+
7
+ def area_map(data_source, **options)
8
+ mapkick_map "AreaMap", data_source, **options
9
+ end
10
+
11
+ private
12
+
13
+ # don't break out options since need to merge with default options
14
+ def mapkick_map(type, data_source, **options)
5
15
  options = Mapkick::Utils.deep_merge(Mapkick.options, options)
6
16
 
7
17
  @mapkick_map_id ||= 0
@@ -67,6 +77,7 @@ module Mapkick
67
77
 
68
78
  # js vars
69
79
  js_vars = {
80
+ type: type,
70
81
  id: element_id,
71
82
  data: data_source,
72
83
  options: options
@@ -74,7 +85,7 @@ module Mapkick
74
85
  js_vars.each_key do |k|
75
86
  js_vars[k] = Mapkick::Utils.json_escape(js_vars[k].to_json)
76
87
  end
77
- createjs = "new Mapkick.Map(%{id}, %{data}, %{options});" % js_vars
88
+ createjs = "new Mapkick[%{type}](%{id}, %{data}, %{options});" % js_vars
78
89
 
79
90
  # don't rerun JS on preview
80
91
  js = <<~JS
@@ -1,3 +1,3 @@
1
1
  module Mapkick
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
@@ -1,5 +1,5 @@
1
1
  ================================================================================
2
- Mapkick.js 0.2.2
2
+ Mapkick.js 0.2.3
3
3
  ================================================================================
4
4
 
5
5
  Copyright (c) 2017-2023 Andrew Kane
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * This bundle includes:
3
3
  *
4
- * Mapkick.js v0.2.2
4
+ * Mapkick.js v0.2.3
5
5
  * https://github.com/ankane/mapkick.js
6
6
  * MIT License
7
7
  *
@@ -304,7 +304,7 @@
304
304
 
305
305
  var maps = {};
306
306
 
307
- var Map = function Map(element, data, options) {
307
+ var BaseMap = function BaseMap(element, data, options, mapType) {
308
308
  var this$1$1 = this;
309
309
 
310
310
  if (!Mapkick.library && typeof window !== "undefined") {
@@ -405,24 +405,28 @@
405
405
  element.textContent = message;
406
406
  }
407
407
 
408
+ function errorCatcher(element, data, options, callback) {
409
+ try {
410
+ callback(element, data, options);
411
+ } catch (err) {
412
+ showError(element, err.message);
413
+ throw err
414
+ }
415
+ }
416
+
408
417
  function fetchData(element, data, options, callback) {
409
418
  if (typeof data === "string") {
410
419
  getJSON(element, data, function (newData) {
411
- callback(element, newData, options);
420
+ errorCatcher(element, newData, options, callback);
412
421
  });
413
422
  } else if (typeof data === "function") {
414
- try {
415
- data(function (newData) {
416
- callback(element, newData, options);
417
- }, function (message) {
418
- showError(element, message);
419
- });
420
- } catch (err) {
421
- showError(element, "Error");
422
- throw err
423
- }
423
+ data(function (newData) {
424
+ errorCatcher(element, newData, options, callback);
425
+ }, function (message) {
426
+ showError(element, message);
427
+ });
424
428
  } else {
425
- callback(element, data, options);
429
+ errorCatcher(element, data, options, callback);
426
430
  }
427
431
  }
428
432
 
@@ -445,21 +449,44 @@
445
449
  for (var i = 0; i < data.length; i++) {
446
450
  var row = data[i];
447
451
  var properties = Object.assign({}, row);
452
+ var geometry = (void 0);
453
+
454
+ if (mapType === "point") {
455
+ if (!properties.icon) {
456
+ properties.icon = options.defaultIcon || "mapkick";
457
+ }
458
+ properties.mapkickIconSize = properties.icon === "mapkick" ? 0.5 : 1;
459
+ properties.mapkickIconAnchor = properties.icon === "mapkick" ? "bottom" : "center";
460
+ properties.mapkickIconOffset = properties.icon === "mapkick" ? [0, 10] : [0, 0];
461
+
462
+ var coordinates = rowCoordinates(row);
463
+
464
+ if (!coordinates[1]) {
465
+ throw new Error(("missing latitude (index: " + i + ")"))
466
+ }
467
+
468
+ if (!coordinates[0]) {
469
+ throw new Error(("missing longitude (index: " + i + ")"))
470
+ }
471
+
472
+ geometry = {
473
+ type: "Point",
474
+ coordinates: coordinates
475
+ };
476
+ } else {
477
+ geometry = row.geometry;
478
+
479
+ if (!geometry) {
480
+ throw new Error(("missing geometry (index: " + i + ")"))
481
+ }
448
482
 
449
- if (!properties.icon) {
450
- properties.icon = options.defaultIcon || "mapkick";
483
+ delete properties.geometry;
451
484
  }
452
- properties.mapkickIconSize = properties.icon === "mapkick" ? 0.5 : 1;
453
- properties.mapkickIconAnchor = properties.icon === "mapkick" ? "bottom" : "center";
454
- properties.mapkickIconOffset = properties.icon === "mapkick" ? [0, 10] : [0, 0];
455
485
 
456
486
  geojson.features.push({
457
487
  type: "Feature",
458
488
  id: i,
459
- geometry: {
460
- type: "Point",
461
- coordinates: rowCoordinates(row),
462
- },
489
+ geometry: geometry,
463
490
  properties: properties
464
491
  });
465
492
  }
@@ -509,42 +536,148 @@
509
536
  return geojson
510
537
  }
511
538
 
539
+ function generateLabelGeoJSON(data) {
540
+ var geojson = {
541
+ type: "FeatureCollection",
542
+ features: []
543
+ };
544
+
545
+ for (var i = 0; i < data.features.length; i++) {
546
+ var feature = data.features[i];
547
+ var coordinates = (void 0);
548
+
549
+ // use center for now
550
+ var bounds = new library.LngLatBounds();
551
+ extendBounds(bounds, feature.geometry);
552
+ if (!bounds.isEmpty()) {
553
+ var center = bounds.getCenter();
554
+ coordinates = [center.lng, center.lat];
555
+ }
556
+
557
+ if (coordinates) {
558
+ geojson.features.push({
559
+ type: "Feature",
560
+ id: i,
561
+ geometry: {
562
+ type: "Point",
563
+ coordinates: coordinates
564
+ },
565
+ properties: feature.properties
566
+ });
567
+ }
568
+ }
569
+
570
+ return geojson
571
+ }
572
+
573
+ function layerBeforeFill(map) {
574
+ // place below labels
575
+ var layers = map.getStyle().layers;
576
+ var beforeId;
577
+ for (var i = layers.length - 1; i >= 0; i--) {
578
+ var layer = layers[i];
579
+ // TODO improve
580
+ if (!(layer.metadata && layer.metadata["mapbox:featureComponent"] === "place-labels")) {
581
+ break
582
+ }
583
+ beforeId = layer.id;
584
+ }
585
+ return beforeId
586
+ }
587
+
512
588
  function addLayer(name, geojson) {
589
+ var centersById = {};
590
+
513
591
  map.addSource(name, {
514
592
  type: "geojson",
515
593
  data: geojson
516
594
  });
517
595
 
518
- // use a symbol layer for markers for performance
519
- // https://docs.mapbox.com/help/getting-started/add-markers/#approach-1-adding-markers-inside-a-map
520
- // use separate layers to prevent labels from overlapping markers
521
- map.addLayer({
522
- id: (name + "-text"),
523
- source: name,
524
- type: "symbol",
525
- layout: {
526
- "text-field": "{label}",
527
- "text-size": 11,
528
- "text-anchor": "top",
529
- "text-offset": [0, 1]
530
- },
531
- paint: {
532
- "text-halo-color": "rgba(255, 255, 255, 1)",
533
- "text-halo-width": 1
534
- }
535
- });
536
- map.addLayer({
537
- id: name,
538
- source: name,
539
- type: "symbol",
540
- layout: {
541
- "icon-image": "{icon}-15",
542
- "icon-allow-overlap": true,
543
- "icon-size": {type: "identity", property: "mapkickIconSize"},
544
- "icon-anchor": {type: "identity", property: "mapkickIconAnchor"},
545
- "icon-offset": {type: "identity", property: "mapkickIconOffset"}
596
+ if (mapType === "point") {
597
+ // use a symbol layer for markers for performance
598
+ // https://docs.mapbox.com/help/getting-started/add-markers/#approach-1-adding-markers-inside-a-map
599
+ // use separate layers to prevent labels from overlapping markers
600
+ map.addLayer({
601
+ id: (name + "-text"),
602
+ source: name,
603
+ type: "symbol",
604
+ layout: {
605
+ "text-field": "{label}",
606
+ "text-size": 11,
607
+ "text-anchor": "top",
608
+ "text-offset": [0, 1]
609
+ },
610
+ paint: {
611
+ "text-halo-color": "rgba(255, 255, 255, 1)",
612
+ "text-halo-width": 1
613
+ }
614
+ });
615
+ map.addLayer({
616
+ id: name,
617
+ source: name,
618
+ type: "symbol",
619
+ layout: {
620
+ "icon-image": "{icon}-15",
621
+ "icon-allow-overlap": true,
622
+ "icon-size": {type: "identity", property: "mapkickIconSize"},
623
+ "icon-anchor": {type: "identity", property: "mapkickIconAnchor"},
624
+ "icon-offset": {type: "identity", property: "mapkickIconOffset"}
625
+ }
626
+ });
627
+ } else {
628
+ var fillColor = markerOptions.color || "#0090ff";
629
+
630
+ var beforeId = layerBeforeFill(map);
631
+
632
+ var outlineId = name + "-outline";
633
+ map.addLayer({
634
+ id: outlineId,
635
+ source: name,
636
+ type: "line",
637
+ paint: {
638
+ "line-color": fillColor,
639
+ "line-opacity": 0.7,
640
+ "line-width": 1
641
+ }
642
+ }, beforeId);
643
+
644
+ map.addLayer({
645
+ id: name,
646
+ source: name,
647
+ type: "fill",
648
+ paint: {
649
+ "fill-color": fillColor,
650
+ "fill-opacity": 0.3
651
+ }
652
+ }, outlineId);
653
+
654
+ var labelName = name + "-text";
655
+ var labelData = generateLabelGeoJSON(geojson);
656
+
657
+ for (var i = 0; i < labelData.features.length; i++) {
658
+ var feature = labelData.features[i];
659
+ centersById[feature.id] = feature.geometry.coordinates;
546
660
  }
547
- });
661
+
662
+ map.addSource(labelName, {
663
+ type: "geojson",
664
+ data: labelData
665
+ });
666
+
667
+ map.addLayer({
668
+ id: (name + "-text"),
669
+ source: labelName,
670
+ type: "symbol",
671
+ layout: {
672
+ "text-field": "{label}",
673
+ "text-size": 11
674
+ },
675
+ paint: {
676
+ "text-halo-color": "rgba(255, 255, 255, 1)",
677
+ "text-halo-width": 1
678
+ }
679
+ });
680
+ }
548
681
 
549
682
  var hover = !("hover" in tooltipOptions) || tooltipOptions.hover;
550
683
 
@@ -597,8 +730,15 @@
597
730
  popup.options.offset = 14;
598
731
  }
599
732
 
733
+ var coordinates;
734
+ if (mapType === "point") {
735
+ coordinates = feature.geometry.coordinates;
736
+ } else {
737
+ coordinates = centersById[feature.id];
738
+ }
739
+
600
740
  // add the tooltip
601
- popup.setLngLat(feature.geometry.coordinates);
741
+ popup.setLngLat(coordinates);
602
742
  if (tooltipOptions.html) {
603
743
  popup.setHTML(tooltip);
604
744
  } else {
@@ -612,7 +752,9 @@
612
752
  popup._container.style.width = popup._container.offsetWidth + 1 + "px";
613
753
  }
614
754
 
615
- panMap(map, popup);
755
+ if (mapType !== "area") {
756
+ panMap(map, popup);
757
+ }
616
758
  };
617
759
 
618
760
  var getLatitude = function (feature) {
@@ -673,12 +815,31 @@
673
815
  });
674
816
  }
675
817
 
818
+ function extendBounds(bounds, geometry) {
819
+ if (geometry.type === "Point") {
820
+ bounds.extend(geometry.coordinates);
821
+ } else if (geometry.type === "Polygon") {
822
+ var coordinates = geometry.coordinates[0];
823
+ for (var j = 0; j < coordinates.length; j++) {
824
+ bounds.extend(coordinates[j]);
825
+ }
826
+ } else if (geometry.type === "MultiPolygon") {
827
+ var coordinates$1 = geometry.coordinates;
828
+ for (var j$1 = 0; j$1 < coordinates$1.length; j$1++) {
829
+ var polygon = coordinates$1[j$1][0];
830
+ for (var k = 0; k < polygon.length; k++) {
831
+ bounds.extend(polygon[k]);
832
+ }
833
+ }
834
+ }
835
+ }
836
+
676
837
  var generateMap = function (element, data, options) {
677
838
  var geojson = generateGeoJSON(data, options);
678
839
  options = options || {};
679
840
 
680
841
  for (var i = 0; i < geojson.features.length; i++) {
681
- bounds.extend(geojson.features[i].geometry.coordinates);
842
+ extendBounds(bounds, geojson.features[i].geometry);
682
843
  }
683
844
 
684
845
  // remove any child elements
@@ -795,11 +956,11 @@
795
956
  }
796
957
  };
797
958
 
798
- Map.prototype.getMapObject = function getMapObject () {
959
+ BaseMap.prototype.getMapObject = function getMapObject () {
799
960
  return this.map
800
961
  };
801
962
 
802
- Map.prototype.destroy = function destroy () {
963
+ BaseMap.prototype.destroy = function destroy () {
803
964
  this.stopRefresh();
804
965
 
805
966
  if (this.map) {
@@ -808,15 +969,40 @@
808
969
  }
809
970
  };
810
971
 
811
- Map.prototype.stopRefresh = function stopRefresh () {
972
+ BaseMap.prototype.stopRefresh = function stopRefresh () {
812
973
  if (this.intervalId) {
813
974
  clearInterval(this.intervalId);
814
975
  this.intervalId = null;
815
976
  }
816
977
  };
817
978
 
979
+ var Map = /*@__PURE__*/(function (BaseMap) {
980
+ function Map(element, data, options) {
981
+ BaseMap.call(this, element, data, options, "point");
982
+ }
983
+
984
+ if ( BaseMap ) Map.__proto__ = BaseMap;
985
+ Map.prototype = Object.create( BaseMap && BaseMap.prototype );
986
+ Map.prototype.constructor = Map;
987
+
988
+ return Map;
989
+ }(BaseMap));
990
+
991
+ var AreaMap = /*@__PURE__*/(function (BaseMap) {
992
+ function AreaMap(element, data, options) {
993
+ BaseMap.call(this, element, data, options, "area");
994
+ }
995
+
996
+ if ( BaseMap ) AreaMap.__proto__ = BaseMap;
997
+ AreaMap.prototype = Object.create( BaseMap && BaseMap.prototype );
998
+ AreaMap.prototype.constructor = AreaMap;
999
+
1000
+ return AreaMap;
1001
+ }(BaseMap));
1002
+
818
1003
  var Mapkick = {
819
1004
  Map: Map,
1005
+ AreaMap: AreaMap,
820
1006
  maps: maps,
821
1007
  options: {},
822
1008
  library: null
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mapkick-rb
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
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-25 00:00:00.000000000 Z
11
+ date: 2023-02-08 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: andrew@ankane.org
@@ -47,7 +47,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
47
47
  - !ruby/object:Gem::Version
48
48
  version: '0'
49
49
  requirements: []
50
- rubygems_version: 3.4.1
50
+ rubygems_version: 3.4.6
51
51
  signing_key:
52
52
  specification_version: 4
53
53
  summary: Create beautiful JavaScript maps with one line of Ruby