jekyll-maplibre 1.0.pre.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8ab808189fd4b1d54aab12429599ebf2cef5ef6579e315fb5eeb8e409324d0e7
4
+ data.tar.gz: 64ee05a010230a5712494c45016fc400f871ad3211d64e576e0df66da780ca55
5
+ SHA512:
6
+ metadata.gz: b6c0ca7186c6e4fa6c8765faddf510bd38f44677e254431e8e1fe4a7214e8d8e0d9aa2cc5a445edbe8af934109fcd1167448d9aca8c547b994a16288bf3601e2
7
+ data.tar.gz: 746f400184fd527e050f6bc35c299ff992079b4c1ce10ba6c2a7a49f1ba17931acdbd782724c75ced778fe5113afd2ebc2e2c5620d5852d597ae5177e11e5183
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ .idea
2
+ *.gem
3
+ Gemfile.lock
4
+ tmp
5
+ spec/dest
6
+ coverage
7
+ _site
8
+ node_modules
9
+ .DS_store
10
+ .#*
data/.rubocop.yml ADDED
@@ -0,0 +1,112 @@
1
+ ---
2
+ AllCops:
3
+ TargetRubyVersion: 3.1
4
+ Include:
5
+ - lib/**/*.rb
6
+ # - spec/**/*.rb
7
+ NewCops: enable
8
+ SuggestExtensions: false
9
+ Layout/EndAlignment:
10
+ Severity: error
11
+ Lint/UnreachableCode:
12
+ Severity: error
13
+ Lint/UselessAccessModifier:
14
+ Enabled: false
15
+ Metrics/AbcSize:
16
+ Max: 20
17
+ Metrics/BlockLength:
18
+ Exclude:
19
+ - spec/**/*.rb
20
+ Metrics/ClassLength:
21
+ Max: 300
22
+ Exclude:
23
+ - !ruby/regexp /spec\/.*.rb$/
24
+ Metrics/CyclomaticComplexity:
25
+ Max: 8
26
+ Layout/LineLength:
27
+ Exclude:
28
+ - Rakefile
29
+ - Gemfile
30
+ - jekyll-maps.gemspec
31
+ Max: 90
32
+ Severity: warning
33
+ Metrics/MethodLength:
34
+ Max: 20
35
+ CountComments: false
36
+ Severity: error
37
+ Metrics/ModuleLength:
38
+ Max: 240
39
+ Metrics/ParameterLists:
40
+ Max: 4
41
+ Metrics/PerceivedComplexity:
42
+ Max: 8
43
+ Style/Alias:
44
+ Enabled: false
45
+ Style/AndOr:
46
+ Severity: error
47
+ Style/Attr:
48
+ Enabled: false
49
+ Style/ClassAndModuleChildren:
50
+ Enabled: false
51
+ Style/Documentation:
52
+ Enabled: false
53
+ Style/DoubleNegation:
54
+ Enabled: false
55
+ Layout/EmptyLinesAroundAccessModifier:
56
+ Enabled: false
57
+ Layout/EmptyLinesAroundModuleBody:
58
+ Enabled: false
59
+ Layout/ExtraSpacing:
60
+ AllowForAlignment: true
61
+ Naming/FileName:
62
+ Enabled: false
63
+ Layout/FirstParameterIndentation:
64
+ EnforcedStyle: consistent
65
+ Style/GuardClause:
66
+ Enabled: false
67
+ Style/HashSyntax:
68
+ EnforcedStyle: hash_rockets
69
+ Severity: error
70
+ Style/IfUnlessModifier:
71
+ Enabled: false
72
+ Layout/IndentationWidth:
73
+ Severity: error
74
+ Style/ModuleFunction:
75
+ Enabled: false
76
+ Layout/MultilineMethodCallIndentation:
77
+ EnforcedStyle: indented
78
+ Layout/MultilineOperationIndentation:
79
+ EnforcedStyle: indented
80
+ Style/MultilineTernaryOperator:
81
+ Severity: error
82
+ Style/PercentLiteralDelimiters:
83
+ PreferredDelimiters:
84
+ "%q": "{}"
85
+ "%Q": "{}"
86
+ "%r": "!!"
87
+ "%s": "()"
88
+ "%w": "()"
89
+ "%W": "()"
90
+ "%x": "()"
91
+ Style/RedundantReturn:
92
+ Enabled: false
93
+ Style/RedundantSelf:
94
+ Enabled: false
95
+ Style/RegexpLiteral:
96
+ EnforcedStyle: percent_r
97
+ Style/RescueModifier:
98
+ Enabled: false
99
+ Style/SignalException:
100
+ EnforcedStyle: only_raise
101
+ Style/SingleLineMethods:
102
+ Enabled: false
103
+ Layout/SpaceAroundOperators:
104
+ Enabled: false
105
+ Style/StringLiterals:
106
+ EnforcedStyle: double_quotes
107
+ Style/StringLiteralsInInterpolation:
108
+ EnforcedStyle: double_quotes
109
+ Style/RedundantCapitalW:
110
+ Enabled: false
111
+ Style/SymbolArray:
112
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ ## 1.0.0-alpha.1
2
+
3
+ * fork jekyll-maps to create jekyll-maplibre
4
+ * remove all code related to Google Maps and use MapLibre GL JS (<https://maplibre.org/maplibre-gl-js/docs/>) instead
5
+ * flexible configuration of sources, layers, styles through page metadata and site config
6
+ * update README.md
7
+ * prepare minimal example
8
+ * prepare a folder with useful assets
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "jekyll", ENV["JEKYLL_VERSION"] if ENV["JEKYLL_VERSION"]
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+ Copyright (c) 2016 Anatoliy Yastreb
3
+ Copyright (c) 2023 Robert Riemann (<robert@riemann.cc>)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,280 @@
1
+ # Jekyll MapLibre
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/jekyll-maplibre.svg)](https://badge.fury.io/rb/jekyll-maplibre)
4
+
5
+ Jekyll MapLibre is a Jekyll plugin that allows to easily display maps with [MapLibre GL JS](https://maplibre.org/). It is a fork of [Jekyll Maps](https://github.com/ayastreb/jekyll-maps) that uses Google Maps.
6
+
7
+ MapLibre GL JS is open source software and allows for self-hosted maps. Self-hosting become much easier with [PMTiles](https://docs.protomaps.com/pmtiles/), which is a single-file archive format for pyramids of tiled data that can be hosted on Github, Gitlab, Netlify, etc. and enables low-cost, zero-maintenance map applications.
8
+
9
+ Check out a Jekyll MapLibre [demo at blog.riemann.cc](https://blog.riemann.cc/projects/jekyll-maplibre)!
10
+
11
+ ## Installation
12
+
13
+ 1. Add the following to your site's `Gemfile`:
14
+
15
+
16
+ ```ruby
17
+ gem 'jekyll-maplibre'
18
+ ```
19
+
20
+ 2. Add the following to your site's `_config.yml`:
21
+
22
+
23
+ ```yml
24
+ plugins:
25
+ - jekyll-maplibre
26
+ ```
27
+
28
+ 3. Also prepare configuration specific for `jekyll-maplibre` in your site's `_config.yml`:
29
+
30
+ ```
31
+ maplibre:
32
+ width: 600
33
+ height: 400
34
+ zoom: 10
35
+ pmtiles: true
36
+ style: false
37
+ # if style is not set to an URL (Mapbox, MapTiler, OpenMapTiles), the following values are used
38
+ sprite: /assets/maps/osm-liberty-sprites/osm-liberty
39
+ glyphs: /assets/maps/fonts/{fontstack}/{range}.pbf
40
+ layers: /assets/maps/OSM-Liberty-layers.json
41
+ sources:
42
+ openmaptiles:
43
+ type: vector
44
+ url: pmtiles:///assets/maps/maptiler-osm-2020-02-10-v3.11-belgium_brussels.pmtiles
45
+ attribution: © <a href='https://openstreetmap.org'>OpenStreetMap contributors</a>
46
+ natural_earth_shaded_relief:
47
+ attribution: Made with <a href='https://www.naturalearthdata.com/'>Natural Earth</a> data
48
+ maxzoom: 6
49
+ type: raster
50
+ url: pmtiles:///assets/maps/natural_earth_2_shaded_relief.raster.pmtiles
51
+ # uncomment tileSize and tiles for cloud CDN tiles
52
+ # tileSize: 256
53
+ # tiles:
54
+ # - "https://klokantech.github.io/naturalearthtiles/tiles/natural_earth_2_shaded_relief.raster/{z}/{x}/{y}.png"
55
+ ```
56
+
57
+ 4. Prepare assets to display maps with MapLibre GL JS:
58
+
59
+ The required assets depend on your specific map configuration. With cloud hosting (untested), it may be sufficient to only configure the `style` property above.
60
+
61
+ For self-hosted maps, you need to host and configure the following resources:
62
+
63
+ - **The source.** PMTiles covering the map area you need. Use `pmtiles convert input.mbtiles output.pmtiles` to convert your mbtiles from e.g. <https://data.maptiler.com/downloads/planet/>. More on `pmtiles` and extraction of custom areas at <https://docs.protomaps.com/guide/getting-started>.
64
+ - **The glyphs.** Font files in pbf format when using vector sources. Check out <https://github.com/openmaptiles/fonts/releases>.
65
+ - **The sprite.** Icons used when using vector sources.
66
+ - **The layers.** A map requires at least one layer. Layers describe how to render source data and rely on glyphs and sprite. The layer definition for vector data depends on the schema used to encode the data in the vector source.
67
+ - **The Mapbox Style file.** It links all assets together.
68
+
69
+ For an easy start `jekyll-maplibre` suggests to use PMTiles raster files and vector files following the *OpenMapTiles Vector Tile Schema*, so that the layers from [OSM-Liberty](https://maputnik.github.io/osm-liberty/) can be used. Find a demo of OSM Liberty [here](https://maputnik.github.io/editor/?style=https://maputnik.github.io/osm-liberty/style.json).
70
+
71
+ Example assets folder:
72
+
73
+ ```
74
+ assets
75
+ └── maps
76
+ ├── fonts
77
+ │   ├── Roboto Condensed Italic
78
+ │   │   ├── 0-255.pbf
79
+ │   │   ├── […]
80
+ │   │   └── 9984-10239.pbf
81
+ │   ├── Roboto Medium
82
+ │   │   ├── 0-255.pbf
83
+ │   │   ├── […]
84
+ │   │   └── 9984-10239.pbf
85
+ │   └── Roboto Regular
86
+ │   ├── 0-255.pbf
87
+ │   ├── […]
88
+ │   └── 9984-10239.pbf
89
+ ├── maplibre-gl.css
90
+ ├── maplibre-gl.js
91
+ ├── maptiler-osm-2020-02-10-v3.11-belgium_brussels.pmtiles
92
+ ├── natural_earth_2_shaded_relief.raster.pmtiles
93
+ ├── natural_earth.vector.pmtiles
94
+ ├── OSM-Liberty-layers.json
95
+ ├── osm-liberty-sprites
96
+ │   ├── osm-liberty@2x.json
97
+ │   ├── osm-liberty@2x.png
98
+ │   ├── osm-liberty.json
99
+ │   └── osm-liberty.png
100
+ └── pmtiles.js
101
+
102
+ ```
103
+
104
+ Example assets sources:
105
+
106
+ - [pmtiles.js](https://unpkg.com/pmtiles@2.11.0/dist/index.js)
107
+ - [osm-liberty-sprites](https://github.com/maputnik/osm-liberty/tree/gh-pages/sprites)
108
+ - [fonts](https://github.com/maputnik/osm-liberty/tree/gh-pages/sprites) (the v2.0 zip)
109
+ - [maplibre-gl.js](https://unpkg.com/maplibre-gl@3.6.2/dist/maplibre-gl.js)
110
+ - [maplibre-gl.css](https://unpkg.com/maplibre-gl@3.6.2/dist/maplibre-gl.css)
111
+ - [OSM-Liberty-layers.json extracted from OSM Liberty style.json](https://github.com/maputnik/osm-liberty/blob/f2c798e80dc11d47613e3b093881b4d37a5fde8e/style.json)
112
+
113
+ 5. Load MapLibre GL JS CSS in `<head>`
114
+
115
+ The file `maplibre-gl.css` should be linked in your `<head>` or included in another css file.
116
+
117
+ Many Jeyll templates provide for a file `_includes/my-head.html` or `_includes/custom-head.html` (check the docs). If so, add a line such as `<link href="/assets/maps/maplibre-gl.css" rel="stylesheet">`.
118
+
119
+ ## Usage
120
+
121
+ ### MapLibre Tag
122
+
123
+ ```
124
+ {% maplibre %}
125
+ ```
126
+
127
+ The following optional attributes are supported:
128
+
129
+ - id
130
+ - width
131
+ - height
132
+ - class
133
+ - style
134
+ - zoom
135
+ - center
136
+ - description
137
+ - latitude
138
+ - longitude
139
+
140
+ The flag `no_cluster` can be used to disable clustering of points.
141
+
142
+ Example with all attributes and flags:
143
+
144
+ ```
145
+ {% maplibre id="custom-id" width="100%" height="200" class="custom-class" style="clear: both;" zoom="10" center="4.300,50.800" description="<a href='#'>Popup Link</a>" longitude="4.300" latitude="50.800" no_cluster %}
146
+ ```
147
+
148
+ ### Data Source
149
+
150
+ Jekyll MapLibre offers several ways to add markers to the map. While in principle MapLibre GL JS allows to add all kinds of data other than markers to the map, more configuration must be added to the style definition.
151
+
152
+ #### Location data in the tag attributes
153
+
154
+ Example:
155
+
156
+ ```
157
+ {% maplibre longitude="4.300" latitude="50.800" %}
158
+ ```
159
+
160
+ #### Location data in the YAML frontmatter
161
+
162
+ ```yml
163
+ location:
164
+ latitude: 50.800
165
+ longitude: 4.300
166
+ ```
167
+
168
+ #### GeoJSON data in the YAML frontmatter
169
+
170
+ The `geojson` attribute in the YAML frontmatter supports (a) individual GeoJSON features or (b) collections of features.
171
+
172
+ Individual feature:
173
+
174
+ ```
175
+ geojson:
176
+ type: Feature
177
+ properties:
178
+ description: |
179
+ <strong>A Little Night Music</strong><p>The Arlington Players' production of Stephen Sondheim's <em>A Little Night Music</em> comes to the Kogod Cradle at The Mead Center for American Theater (1101 6th Street SW) this weekend and next. 8:00 p.m.</p>
180
+ geometry:
181
+ type: Point
182
+ coordinates: [4.300, 50.800]
183
+ ```
184
+
185
+ Collection of features:
186
+
187
+ ```
188
+ geojson:
189
+ type: FeatureCollection
190
+ features:
191
+ -
192
+ type: Feature
193
+ properties:
194
+ description: "<strong>A Little Night Music</strong><p>The Arlington Players' production of Stephen Sondheim's <em>A Little Night Music</em> comes to the Kogod Cradle at The Mead Center for American Theater (1101 6th Street SW) this weekend and next. 8:00 p.m.</p>\n"
195
+ geometry:
196
+ type: Point
197
+ coordinates: [4.376, - 50.83012]
198
+ -
199
+ type: Feature
200
+ ...
201
+ ```
202
+
203
+ #### GeoJSON data in a JSON file
204
+
205
+ The `geojson` attribute in the YAML frontmatter can also contain a URL with `.json` extension.
206
+
207
+ Example:
208
+
209
+ ```
210
+ geojson: /post-locations.json`
211
+ ```
212
+
213
+ The linked file can be a static file on the same host or another server. If the data source points to a generated file capturing the `location` attribute of posts, Jekyll MapLibre can display maps with post markers.
214
+
215
+ Example for `/post-locations.json`:
216
+
217
+ ```
218
+ {% assign posts = site.posts | where_exp:"location", "location != nil" %}
219
+ {
220
+ "type": "FeatureCollection",
221
+ "features": [
222
+ {% for post in posts limit:20 %}
223
+ {
224
+ "type": "Feature",
225
+ "properties": {
226
+ "description": "<b><a href='{{post.url}}'>{{post.title}}</a></b><br/>{{post.description}}<br/>"
227
+ },
228
+ "geometry": {
229
+ "type": "Point",
230
+ "coordinates": {{post.location | jsonify }}
231
+ }
232
+ }{% unless forloop.last %},{% endif %}
233
+ {% endfor %}
234
+ ]
235
+ }
236
+ ```
237
+
238
+ With `where` and `where_exp` (see [documentation](https://jekyllrb.com/docs/liquid/filters/)), Jekyll permits to implement various filters.
239
+
240
+ ### Marker Cluster
241
+
242
+ Clusters are enabled by default. Use the flag `no_cluster` in the tag to disable clusters.
243
+
244
+ ## Examples
245
+
246
+ Want to see Jekyll MapLibre in action? Check out [Demo Page](https://ayastreb.me/jekyll-maps/#examples)!
247
+
248
+ ## Contributing
249
+
250
+ 1. Fork it (https://github.com/rriemann/jekyll-maplibre/fork)
251
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
252
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
253
+ 4. Push to the branch (`git push origin my-new-feature`)
254
+ 5. Create a new Pull Request
255
+
256
+ ## TODOs
257
+
258
+ The following issues and limitation require contribution:
259
+
260
+ - implement random SVG markers, see: <https://github.com/maplibre/maplibre-gl-js/discussions/3243>, <https://github.com/rbrundritt/maplibre-gl-svg/>
261
+ - implement more than one map tag per page
262
+ - add more examples on how to generate geojson data from Jekyll collections/data.
263
+ - Jekyll-Maps has spec tests (still in this repo) – make them work again with Jekyll MapLibre
264
+ - add flag to switch popups to open by default (without click to open)
265
+
266
+ ## Similar Software
267
+
268
+ - <https://github.com/ayastreb/jekyll-maps/>
269
+ - <https://github.com/matthewowen/jekyll-mapping>
270
+ - <https://wiki.openstreetmap.org/wiki/UMap>
271
+
272
+ ## Contributors
273
+
274
+ - Anatoliy Yastreb (as the author of the forked Jekyll Maps gem)
275
+ - Robert Riemann
276
+
277
+ ## License
278
+
279
+ [MIT](https://github.com/rriemann/jekyll-maplibre/blob/master/LICENSE). Feel free to use, copy or
280
+ distribute it.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,27 @@
1
+ lib = File.expand_path("lib", __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require "jekyll-maplibre/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "jekyll-maplibre"
8
+ spec.summary = "Maplibre GL JS for Jekyll"
9
+ spec.description = "MapLibre GL JS support for Jekyll websites to easily display vector maps with geojson data"
10
+ spec.version = Jekyll::MapLibre::VERSION
11
+ spec.authors = ["Anatoliy Yastreb, Robert Riemann"]
12
+ spec.email = ["robert@riemann.cc"]
13
+
14
+ spec.homepage = "https://blog.riemann.cc/projects/jekyll-maplibre"
15
+ spec.licenses = ["MIT"]
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r!^(test|spec|features)/!) }
17
+ spec.require_paths = ["lib"]
18
+
19
+ spec.required_ruby_version = '>= 3.1.0'
20
+
21
+ spec.add_dependency "jekyll", ">= 3.0", "< 5.0"
22
+
23
+ spec.add_development_dependency "bundler"
24
+ spec.add_development_dependency "rake", "~> 13.1"
25
+ spec.add_development_dependency "rspec", "~> 3.5"
26
+ spec.add_development_dependency "rubocop", "1.50.2"
27
+ end
@@ -0,0 +1,151 @@
1
+ <script src="/assets/maps/maplibre-gl.js"></script>
2
+ <% if context.registers[:site].config.dig("maplibre","pmtiles") %>
3
+ <script src="/assets/maps/pmtiles.js"></script>
4
+ <script type="text/javascript">
5
+ // add the PMTiles plugin to the maplibregl global.
6
+ let protocol = new pmtiles.Protocol();
7
+ maplibregl.addProtocol("pmtiles",protocol.tile);
8
+ </script>
9
+ <% end %>
10
+ <div <%= attributes(context) %>></div>
11
+
12
+ <script type="text/javascript">
13
+ (async () => {
14
+ const map = new maplibregl.Map({
15
+ container: '<%= @options[:attributes][:id] %>',
16
+ zoom: <%= zoom %>,
17
+ center: <%= center(context).to_json %>,
18
+ style: <%= style(context) %>,
19
+ });
20
+ // map.showTileBoundaries = true;
21
+
22
+ // Add zoom and rotation controls to the map.
23
+ map.addControl(new maplibregl.NavigationControl());
24
+
25
+ <% if geojson(context) %>
26
+ map.on('load', () => {
27
+ // Add a new source from our GeoJSON data and
28
+ // set the 'cluster' option to true. GL-JS will
29
+ // add the point_count property to your source data.
30
+ map.addSource('jekyll-data', {
31
+ type: 'geojson',
32
+ data: <%= geojson(context).to_json %>,
33
+ cluster: <%= (!@options[:flags][:no_cluster]).to_s %>,
34
+ clusterMaxZoom: 14, // Max zoom to cluster points on
35
+ clusterRadius: 50 // Radius of each cluster when clustering points (defaults to 50)
36
+ });
37
+
38
+ map.addLayer({
39
+ id: 'clusters',
40
+ type: 'circle',
41
+ source: 'jekyll-data',
42
+ filter: ['has', 'point_count'],
43
+ paint: {
44
+ // Use step expressions (https://maplibre.org/maplibre-style-spec/#expressions-step)
45
+ // with three steps to implement three types of circles:
46
+ // * Blue, 20px circles when point count is less than 100
47
+ // * Yellow, 30px circles when point count is between 100 and 750
48
+ // * Pink, 40px circles when point count is greater than or equal to 750
49
+ 'circle-color': [
50
+ 'step',
51
+ ['get', 'point_count'],
52
+ '#51bbd6',
53
+ 100,
54
+ '#f1f075',
55
+ 750,
56
+ '#f28cb1'
57
+ ],
58
+ 'circle-radius': [
59
+ 'step',
60
+ ['get', 'point_count'],
61
+ 20,
62
+ 100,
63
+ 30,
64
+ 750,
65
+ 40
66
+ ]
67
+ }
68
+ });
69
+
70
+ map.addLayer({
71
+ id: 'cluster-count',
72
+ type: 'symbol',
73
+ source: 'jekyll-data',
74
+ filter: ['has', 'point_count'],
75
+ layout: {
76
+ 'text-field': '{point_count_abbreviated}',
77
+ 'text-font': ['Roboto Regular'],
78
+ 'text-size': 12
79
+ }
80
+ });
81
+
82
+ map.addLayer({
83
+ id: 'unclustered-point',
84
+ type: 'symbol',
85
+ source: 'jekyll-data',
86
+ filter: ['!', ['has', 'point_count']],
87
+ layout: {
88
+ "icon-image": "marker",
89
+ "icon-size": 2,
90
+ "icon-anchor": "bottom",
91
+ }
92
+ });
93
+
94
+ // inspect a cluster on click
95
+ map.on('click', 'clusters', (e) => {
96
+ const features = map.queryRenderedFeatures(e.point, {
97
+ layers: ['clusters']
98
+ });
99
+ const clusterId = features[0].properties.cluster_id;
100
+ map.getSource('jekyll-data').getClusterExpansionZoom(
101
+ clusterId,
102
+ (err, zoom) => {
103
+ if (err) return;
104
+
105
+ map.easeTo({
106
+ center: features[0].geometry.coordinates,
107
+ zoom
108
+ });
109
+ }
110
+ );
111
+ });
112
+
113
+ // When a click event occurs on a feature in
114
+ // the unclustered-point layer, open a popup at
115
+ // the location of the feature, with
116
+ // description HTML from its properties.
117
+ map.on('click', 'unclustered-point', (e) => {
118
+ if(!e.features[0].properties.description) return;
119
+ const coordinates = e.features[0].geometry.coordinates.slice();
120
+
121
+ // Ensure that if the map is zoomed out such that
122
+ // multiple copies of the feature are visible, the
123
+ // popup appears over the copy being pointed to.
124
+ while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
125
+ coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
126
+ }
127
+
128
+ new maplibregl.Popup({offset: [0, -35]})
129
+ .setLngLat(coordinates)
130
+ .setHTML(e.features[0].properties.description)
131
+ .addTo(map);
132
+ });
133
+
134
+ map.on('mouseenter', 'unclustered-point', () => {
135
+ if(!e.features[0].properties.description) return;
136
+ map.getCanvas().style.cursor = 'pointer';
137
+ });
138
+ map.on('mouseleave', 'unclustered-point', () => {
139
+ map.getCanvas().style.cursor = '';
140
+ });
141
+
142
+ map.on('mouseenter', 'clusters', () => {
143
+ map.getCanvas().style.cursor = 'pointer';
144
+ });
145
+ map.on('mouseleave', 'clusters', () => {
146
+ map.getCanvas().style.cursor = '';
147
+ });
148
+ });
149
+ <% end %>
150
+ })();
151
+ </script>
@@ -0,0 +1,221 @@
1
+ ##
2
+ # copyright: 2020 Anatoliy Yastreb <anatoliy.yastreb@gmail.com>,
3
+ # 2023 Robert Riemann <robert@riemann.cc>
4
+ # license: MIT
5
+ #
6
+ # original code from jekyll-maps, https://github.com/ayastreb/jekyll-maps/
7
+
8
+ require 'json'
9
+ require 'erb'
10
+
11
+ # taken from Active Support Gem (MIT license)
12
+ class Hash
13
+ # File activesupport/lib/active_support/core_ext/hash/keys.rb, line 82
14
+ def deep_transform_keys(&block)
15
+ result = {}
16
+ each do |key, value|
17
+ result[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys(&block) : value
18
+ end
19
+ result
20
+ end
21
+
22
+ # File activesupport/lib/active_support/core_ext/hash/keys.rb, line 128
23
+ def deep_symbolize_keys
24
+ deep_transform_keys{ |key| key.to_sym rescue key }
25
+ end
26
+ end
27
+
28
+ module Jekyll
29
+ module MapLibre
30
+
31
+ # @author Robert Riemann <robert@riemann.cc>
32
+ class MapLibreTag < Liquid::Tag
33
+ DEFAULT_MAP_WIDTH = 600
34
+ DEFAULT_MAP_HEIGHT = 400
35
+ DEFAULT_ZOOM = 10
36
+
37
+ def initialize(tag_name, args, tokens)
38
+ @options = OptionsParser.parse(args)
39
+ super
40
+ end
41
+
42
+ # @return [String] HTML code with JS to render map with MapLibre GL
43
+ def render(context)
44
+ zoom = @options[:attributes][:zoom] || context.registers[:site].config.dig('maplibre', 'zoom') || DEFAULT_ZOOM
45
+
46
+ @options[:attributes][:id] ||= "maplibre-#{SecureRandom.uuid}"
47
+
48
+ template = ERB.new File.read(File.expand_path("maplibre.html.erb", __dir__))
49
+ template.result(binding)
50
+ end
51
+
52
+ private
53
+ # @return [String] absolute URL for sprite property pursuant to Mapbox Style Spec
54
+ def sprite_url(context)
55
+ # note: unlike glyphs, sprites must be an absolute path with protocol
56
+ # see: https://github.com/mapbox/mapbox-gl-js/pull/9225
57
+ sprite_config = context.registers[:site].config["maplibre"]["sprite"]
58
+ if sprite_config.start_with?('/') then
59
+ "#{context.registers[:site].config["url"]}#{sprite_config}"
60
+ else
61
+ sprite_config
62
+ end
63
+ end
64
+
65
+ private
66
+ # Generates from page metadata and site config
67
+ # the style object pursuant to the Mapbox Style Spec
68
+ #
69
+ # @note The output can include some JS code to load layers asynchronously
70
+ # and is as such not always valid JSON.
71
+ # @return [String] style persuant to Mapbox Style Spec
72
+ def style(context)
73
+ page_config = context.registers[:page].to_hash
74
+ site_config = context.registers[:site].config
75
+
76
+ if page_config.dig('maplibre', 'style') then
77
+ page_config.dig('maplibre', 'style').to_json
78
+ elsif site_config.dig('maplibre', 'style') then
79
+ site_config.dig('maplibre', 'style').to_json
80
+ else
81
+ {
82
+ version: 8,
83
+ name: "OSM Liberty",
84
+ metadata: {
85
+ "maputnik:license": "https://github.com/maputnik/osm-liberty/blob/gh-pages/LICENSE.md",
86
+ "maputnik:renderer": "mbgljs",
87
+ "openmaptiles:version": "3.x"
88
+ },
89
+ sources: sources(context),
90
+ sprite: sprite_url(context),
91
+ glyphs: context.registers[:site].config["maplibre"]["glyphs"],
92
+ layers: "__LAYERS__",
93
+ id: "osm-liberty"
94
+ }.to_json.sub(%r{"__LAYERS__"}, layers(context)) # assumes that #to_json uses double quotes
95
+ end
96
+ end
97
+
98
+ private
99
+ # Generates from page metadata and site config the sources
100
+ # for the style object pursuant to the Mapbox Style Spec
101
+ #
102
+ # @return [Hash] Mapbox Style sources
103
+ def sources(context)
104
+ page_config = context.registers[:page].to_hash
105
+ site_config = context.registers[:site].config
106
+
107
+ if page_config.dig('maplibre', 'sources') then
108
+ page_config.dig('maplibre', 'sources')
109
+ elsif site_config.dig('maplibre', 'sources') then
110
+ site_config.dig('maplibre', 'sources')
111
+ else
112
+ raise "No MapLibre source found in site config and page meta data."
113
+ end
114
+ end
115
+
116
+ private
117
+ # Generates from page metadata and site config the layers
118
+ # for the style object pursuant to the Mapbox Style Spec
119
+ #
120
+ # @note The string is either some JS code to load a JSON file asynchronously or directly a JSON Hash.
121
+ # @return [String] Mapbox Style layers
122
+ def layers(context)
123
+ page_config = context.registers[:page].to_hash
124
+ site_config = context.registers[:site].config
125
+
126
+ unless page_config.dig('maplibre', 'layers') or site_config.dig('maplibre', 'layers') then
127
+ raise "No MapLibre layers found in site config and page meta data."
128
+ end
129
+
130
+ obj = page_config.dig('maplibre', 'layers') || site_config.dig('maplibre', 'layers')
131
+ if obj&.end_with? '.json' then
132
+ "await (await fetch('#{obj}')).json()"
133
+ else
134
+ obj.to_json
135
+ end
136
+ end
137
+
138
+ private
139
+ # LngLat array from tag options or geojson to center the map
140
+ #
141
+ # @return [Array,nil] LngLat array or nil
142
+ def center(context)
143
+ @options[:attributes][:center]&.map{|v| v.to_f} || geojson(context).dig(:features, 0, :geometry, :coordinates)
144
+ end
145
+
146
+ private
147
+ # Generates map div attributes
148
+ #
149
+ # @return [String]
150
+ def attributes(context)
151
+ attr = []
152
+ attr << "id='#{@options[:attributes][:id]}'"
153
+ attr << %(class='#{Array(@options[:attributes][:class]).join(" ")}') if @options[:attributes][:class]
154
+ attr << %(style='#{(Array(@options[:attributes][:style]) + dimensions(context)).join(";")}')
155
+ attr.join(" ")
156
+ end
157
+
158
+ private
159
+ # Generates map div css with dimensions
160
+ #
161
+ # @return [String]
162
+ def dimensions(context)
163
+ width = @options[:attributes][:width] || context.registers[:site].config.dig('maplibre', 'width') || DEFAULT_MAP_WIDTH
164
+ height = @options[:attributes][:height] || context.registers[:site].config.dig('maplibre', 'height') || DEFAULT_MAP_HEIGHT
165
+ width_unit = width.to_s.include?("%") ? "" : "px"
166
+ height_unit = height.to_s.include?("%") ? "" : "px"
167
+ ["width:#{width}#{width_unit}", "height:#{height}#{height_unit}"]
168
+ end
169
+
170
+ private
171
+ # Generates GeoJSON Hash from tag attributes or page metadata
172
+ #
173
+ # @return [Hash] GeoJSON Hash
174
+ def geojson(context)
175
+ @geojson ||= if @options[:attributes][:latitude] and @options[:attributes][:longitude] then
176
+ {
177
+ type: "FeatureCollection",
178
+ features: [{
179
+ type: "Feature",
180
+ # properties: {}.select {|key, value| !value.nil? },
181
+ geometry: {
182
+ type: "Point",
183
+ coordinates: [ # first long, that lat
184
+ @options[:attributes]["longitude"].to_f,
185
+ @options[:attributes]["latitude"].to_f,
186
+ ]
187
+ }
188
+ }],
189
+ }
190
+ elsif (context.registers[:page]["geojson"]&.is_a?(String) and context.registers[:page]["geojson"]&.end_with?(".json")) or
191
+ context.registers[:page]["geojson"]["type"] == "FeatureCollection" then
192
+ context.registers[:page]["geojson"].deep_symbolize_keys
193
+ elsif context.registers[:page]["geojson"]["type"] == "Feature" then
194
+ {
195
+ type: "FeatureCollection",
196
+ features: [context.registers[:page]["geojson"].deep_symbolize_keys]
197
+ }
198
+ elsif context.registers[:page]["location"]["latitude"] and context.registers[:page]["location"]["longitude"] then
199
+ {
200
+ type: "FeatureCollection",
201
+ features: [{
202
+ type: "Feature",
203
+ # properties: {}.select {|key, value| !value.nil? },
204
+ geometry: {
205
+ type: "Point",
206
+ coordinates: [ # first long, that lat
207
+ context.registers[:page]["location"]["longitude"].to_f,
208
+ context.registers[:page]["location"]["latitude"].to_f,
209
+ ]
210
+ }
211
+ }]
212
+ }
213
+ else
214
+ nil
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end
220
+
221
+ Liquid::Template.register_tag("maplibre", Jekyll::MapLibre::MapLibreTag)
@@ -0,0 +1,49 @@
1
+ ##
2
+ # copyright: 2020 Anatoliy Yastreb <anatoliy.yastreb@gmail.com>
3
+ # license: MIT
4
+ #
5
+ # original code from jekyll-maps, https://github.com/ayastreb/jekyll-maps/
6
+
7
+ module Jekyll
8
+ module MapLibre
9
+ class OptionsParser
10
+ OPTIONS_SYNTAX = %r!([^\s]+)\s*=\s*['"]+([^'"]+)['"]+!
11
+ ALLOWED_FLAGS = %w(
12
+ no_cluster
13
+ ).freeze
14
+ ALLOWED_ATTRIBUTES = %w(
15
+ id
16
+ width
17
+ height
18
+ class
19
+ style
20
+ zoom
21
+ center
22
+ description
23
+ latitude
24
+ longitude
25
+ ).freeze
26
+
27
+ class << self
28
+ def parse(raw_options)
29
+ options = {
30
+ :attributes => {},
31
+ :flags => {}
32
+ }
33
+ raw_options.scan(OPTIONS_SYNTAX).each do |key, value|
34
+ value = value.split(",") if value.include?(",")
35
+ if ALLOWED_ATTRIBUTES.include?(key)
36
+ options[:attributes][key.to_sym] = value
37
+ else
38
+ raise "found not allowed MapLibre tag attribute #{key}"
39
+ end
40
+ end
41
+ ALLOWED_FLAGS.each do |key|
42
+ options[:flags][key.to_sym] = true if raw_options.include?(key)
43
+ end
44
+ options
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,5 @@
1
+ module Jekyll
2
+ module MapLibre
3
+ VERSION = "1.0-alpha.1".freeze
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ require "securerandom"
2
+
3
+ require_relative "jekyll-maplibre/options_parser"
4
+ require_relative "jekyll-maplibre/version"
5
+
6
+ require_relative "jekyll-maplibre/maplibre_tag"
7
+
8
+ module Jekyll
9
+ module MapLibre
10
+ end
11
+ end
data/script/bootstrap ADDED
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ bundle install
data/script/cibuild ADDED
@@ -0,0 +1,4 @@
1
+ #! /bin/sh
2
+
3
+ bundle exec rspec --color $@
4
+ bundle exec rubocop -S -D
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jekyll-maplibre
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.pre.alpha.1
5
+ platform: ruby
6
+ authors:
7
+ - Anatoliy Yastreb, Robert Riemann
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-12-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: jekyll
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '5.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '3.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: bundler
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '13.1'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '13.1'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rspec
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '3.5'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '3.5'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rubocop
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - '='
80
+ - !ruby/object:Gem::Version
81
+ version: 1.50.2
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - '='
87
+ - !ruby/object:Gem::Version
88
+ version: 1.50.2
89
+ description: MapLibre GL JS support for Jekyll websites to easily display vector maps
90
+ with geojson data
91
+ email:
92
+ - robert@riemann.cc
93
+ executables: []
94
+ extensions: []
95
+ extra_rdoc_files: []
96
+ files:
97
+ - ".gitignore"
98
+ - ".rubocop.yml"
99
+ - CHANGELOG.md
100
+ - Gemfile
101
+ - LICENSE
102
+ - README.md
103
+ - Rakefile
104
+ - jekyll-maplibre.gemspec
105
+ - lib/jekyll-maplibre.rb
106
+ - lib/jekyll-maplibre/maplibre.html.erb
107
+ - lib/jekyll-maplibre/maplibre_tag.rb
108
+ - lib/jekyll-maplibre/options_parser.rb
109
+ - lib/jekyll-maplibre/version.rb
110
+ - script/bootstrap
111
+ - script/cibuild
112
+ homepage: https://blog.riemann.cc/projects/jekyll-maplibre
113
+ licenses:
114
+ - MIT
115
+ metadata: {}
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: 3.1.0
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">"
128
+ - !ruby/object:Gem::Version
129
+ version: 1.3.1
130
+ requirements: []
131
+ rubygems_version: 3.3.7
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: Maplibre GL JS for Jekyll
135
+ test_files: []