jekyll-map 0.2.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ee7f133aff0e9c08366d7fe4bec2747f457add3d2684288411bab98ad4d76414
4
+ data.tar.gz: b422a0a6bc9ac0cb0b56a0be45eddc990b471a8565928d0789d0897d51928a6f
5
+ SHA512:
6
+ metadata.gz: 89f62d0c9fea41d42e255032cb06d49f65a60aa73ca253fac9cecd47478e4bfe1838ee3385bc68b621d8f1f6de31f5267b80462136339df699ebaa41c8eae443
7
+ data.tar.gz: 1233b2d685cf729cc464558b58b6fddc35e539fd03503c9c87cc74ad98ef1a777b604327c31933bb7a0d66a937e51852053062336f963999c80fa46061d9fa02
data/CHANGELOG.md ADDED
@@ -0,0 +1,45 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.2.0] - 2026-08-20
9
+
10
+ ### Added
11
+ - Google Static Maps provider support (`provider: google`)
12
+ - Google Geocoding API support for address-string geocoding with Google provider
13
+ - Per-tag `provider=` override (e.g. `{% map lat=N lng=N provider=google %}`)
14
+ - Google map types: roadmap, satellite, terrain, hybrid
15
+
16
+ ### Changed
17
+ - Config block moved from `mapbox:` to `map:` with nested `mapbox:` and `google:` sections
18
+ - `map.provider` sets the default provider (default: `mapbox`)
19
+
20
+ ### Upgrading from 0.1.0
21
+
22
+ Replace your `_config.yml` `mapbox:` block:
23
+
24
+ ```yaml
25
+ # Before (0.1.x)
26
+ mapbox:
27
+ token: pk.xxx
28
+
29
+ # After (0.2.x)
30
+ map:
31
+ provider: mapbox
32
+ mapbox:
33
+ token: pk.xxx
34
+ ```
35
+
36
+ ## [0.1.0] - 2026-08-20
37
+
38
+ ### Added
39
+ - Initial release
40
+ - `{% map %}` Liquid tag for embedding Mapbox static map images
41
+ - Latitude/longitude direct input
42
+ - Address string geocoding via Mapbox Geocoding API with local cache (`_map_cache.json`)
43
+ - Parameters: `zoom`, `width`, `height`, `marker`, `style`, `alt`
44
+ - Map style aliases: streets, satellite, outdoors, light, dark
45
+ - Config-level defaults via `mapbox:` block in `_config.yml`
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jason Chance
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,142 @@
1
+ # jekyll-map
2
+
3
+ A Jekyll plugin that adds a `{% map %}` Liquid tag for embedding static map images. No JavaScript required — just a responsive `<img>` tag. Supports both **Mapbox** and **Google Static Maps** as providers.
4
+
5
+ ## Installation
6
+
7
+ Add the gem to your Jekyll site's `Gemfile`:
8
+
9
+ ```ruby
10
+ gem "jekyll-map", "~> 0.2"
11
+ ```
12
+
13
+ Then add it to the `plugins` list in `_config.yml`:
14
+
15
+ ```yaml
16
+ plugins:
17
+ - jekyll-map
18
+ ```
19
+
20
+ Run `bundle install` to install the gem.
21
+
22
+ ## Configuration
23
+
24
+ Add a `map:` block to `_config.yml`. Set `provider` to `mapbox` (default) or `google`, and include credentials for whichever provider(s) you use.
25
+
26
+ ```yaml
27
+ map:
28
+ provider: mapbox # default provider: mapbox or google
29
+ zoom: 13 # optional default zoom
30
+ width: 800 # optional default width
31
+ height: 400 # optional default height
32
+ mapbox:
33
+ token: pk.your_public_access_token_here
34
+ style: streets # optional: streets, satellite, outdoors, light, dark
35
+ google:
36
+ key: AIzaSy...
37
+ style: roadmap # optional: roadmap, satellite, terrain, hybrid
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ ### By latitude and longitude
43
+
44
+ ```liquid
45
+ {% map lat=33.749 lng=-84.388 %}
46
+ ```
47
+
48
+ ### By address string
49
+
50
+ Pass a quoted address string and the plugin geocodes it at build time using the active provider's geocoding API. Results are cached in `_map_cache.json`.
51
+
52
+ ```liquid
53
+ {% map "100 S Hill Street, Griffin, GA" zoom=15 %}
54
+ ```
55
+
56
+ ### Override provider per tag
57
+
58
+ ```liquid
59
+ {% map lat=33.749 lng=-84.388 provider=google %}
60
+ {% map "City Hall, Atlanta, GA" provider=mapbox zoom=14 %}
61
+ ```
62
+
63
+ ### Commit `_map_cache.json`
64
+
65
+ Commit `_map_cache.json` to your repo so CI builds don't re-geocode addresses on every run.
66
+
67
+ ## Parameters
68
+
69
+ All parameters are optional and override values in `_config.yml`.
70
+
71
+ | Parameter | Default | Description |
72
+ | --- | --- | --- |
73
+ | `lat` | — | Latitude (required if no address string) |
74
+ | `lng` | — | Longitude (required if no address string) |
75
+ | `zoom` | `13` | Map zoom level (0–22) |
76
+ | `width` | `800` | Image width in pixels |
77
+ | `height` | `400` | Image height in pixels |
78
+ | `marker` | `true` | Show a marker at the center point |
79
+ | `style` | `streets` / `roadmap` | Map style (see below) |
80
+ | `provider` | config value | Override provider for this tag: `mapbox` or `google` |
81
+ | `alt` | `"Map"` | Alt text for the `<img>` tag |
82
+
83
+ ### Mapbox styles
84
+
85
+ | Value | Mapbox style ID |
86
+ | --- | --- |
87
+ | `streets` | streets-v12 |
88
+ | `satellite` | satellite-v9 |
89
+ | `outdoors` | outdoors-v12 |
90
+ | `light` | light-v11 |
91
+ | `dark` | dark-v11 |
92
+
93
+ ### Google map types
94
+
95
+ | Value | Description |
96
+ | --- | --- |
97
+ | `roadmap` | Standard road map (default) |
98
+ | `satellite` | Satellite imagery |
99
+ | `terrain` | Topographic map |
100
+ | `hybrid` | Satellite with road labels |
101
+
102
+ ## Examples
103
+
104
+ ```liquid
105
+ {% map lat=33.749 lng=-84.388 %}
106
+
107
+ {% map "City Hall, Decatur, GA" zoom=15 %}
108
+
109
+ {% map lat=33.749 lng=-84.388 style=satellite zoom=16 width=1200 height=500 %}
110
+
111
+ {% map "Atlanta, GA" marker=false style=light alt="Atlanta metro area" %}
112
+
113
+ {% map "Griffin, GA" provider=google style=hybrid zoom=14 %}
114
+ ```
115
+
116
+ ## Output
117
+
118
+ The tag renders a single `<img>` element:
119
+
120
+ ```html
121
+ <img src="https://api.mapbox.com/..." alt="Map" width="800" height="400" class="jekyll-map" loading="lazy">
122
+ ```
123
+
124
+ Add styles for `.jekyll-map` in your site's CSS:
125
+
126
+ ```css
127
+ .jekyll-map {
128
+ width: 100%;
129
+ height: auto;
130
+ border-radius: 4px;
131
+ }
132
+ ```
133
+
134
+ ## GitHub Pages compatibility
135
+
136
+ This plugin is compatible with Jekyll sites deployed to GitHub Pages when the site is built through a custom GitHub Actions workflow.
137
+
138
+ It is not compatible with the default GitHub Pages safe-mode build, because custom plugins are not loaded there.
139
+
140
+ ## License
141
+
142
+ MIT License. See [LICENSE.txt](LICENSE.txt).
@@ -0,0 +1,230 @@
1
+ require "jekyll"
2
+ require "net/http"
3
+ require "uri"
4
+ require "json"
5
+ require "cgi"
6
+
7
+ module Jekyll
8
+ class MapTag < Liquid::Tag
9
+ # Mapbox
10
+ MAPBOX_STATIC_BASE = "https://api.mapbox.com/styles/v1/mapbox/%s/static"
11
+ MAPBOX_GEOCODE_BASE = "https://api.mapbox.com/geocoding/v5/mapbox.places/%s.json"
12
+
13
+ MAPBOX_STYLE_ALIASES = {
14
+ "streets" => "streets-v12",
15
+ "satellite" => "satellite-v9",
16
+ "outdoors" => "outdoors-v12",
17
+ "light" => "light-v11",
18
+ "dark" => "dark-v11"
19
+ }.freeze
20
+
21
+ # Google
22
+ GOOGLE_STATIC_BASE = "https://maps.googleapis.com/maps/api/staticmap"
23
+ GOOGLE_GEOCODE_BASE = "https://maps.googleapis.com/maps/api/geocode/json"
24
+
25
+ GOOGLE_MAPTYPE_ALIASES = {
26
+ "streets" => "roadmap",
27
+ "roadmap" => "roadmap",
28
+ "satellite" => "satellite",
29
+ "terrain" => "terrain",
30
+ "hybrid" => "hybrid"
31
+ }.freeze
32
+
33
+ DEFAULT_ZOOM = 13
34
+ DEFAULT_WIDTH = 800
35
+ DEFAULT_HEIGHT = 400
36
+ DEFAULT_PROVIDER = "mapbox"
37
+
38
+ def initialize(tag_name, markup, tokens)
39
+ super
40
+ @raw_markup = markup.strip
41
+ end
42
+
43
+ def render(context)
44
+ site = context.registers[:site]
45
+ map_config = site.config["map"] || {}
46
+ params = parse_markup(@raw_markup)
47
+
48
+ provider = params["provider"] || map_config["provider"] || DEFAULT_PROVIDER
49
+
50
+ zoom = (params["zoom"] || map_config["zoom"] || DEFAULT_ZOOM).to_i
51
+ width = (params["width"] || map_config["width"] || DEFAULT_WIDTH).to_i
52
+ height = (params["height"] || map_config["height"] || DEFAULT_HEIGHT).to_i
53
+ marker = truthy?(params.fetch("marker", "true"))
54
+ alt = params["alt"] || "Map"
55
+
56
+ case provider
57
+ when "google"
58
+ render_google(params, map_config, site, zoom, width, height, marker, alt)
59
+ else
60
+ render_mapbox(params, map_config, site, zoom, width, height, marker, alt)
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ # -------------------------------------------------------------------------
67
+ # Mapbox
68
+ # -------------------------------------------------------------------------
69
+
70
+ def render_mapbox(params, map_config, site, zoom, width, height, marker, alt)
71
+ mapbox_config = map_config["mapbox"] || {}
72
+ token = mapbox_config["token"]
73
+ raise "jekyll-map: map.mapbox.token is required in _config.yml" if token.nil? || token.strip.empty?
74
+
75
+ lat, lng = resolve_coordinates_mapbox(params, token, site)
76
+ raise "jekyll-map: could not resolve coordinates from markup: #{@raw_markup}" if lat.nil?
77
+
78
+ style = resolve_mapbox_style(params["style"] || mapbox_config["style"] || "streets")
79
+ url = build_mapbox_url(lat, lng, zoom, width, height, marker, style, token)
80
+
81
+ img_tag(url, alt, width, height)
82
+ end
83
+
84
+ def resolve_mapbox_style(style)
85
+ MAPBOX_STYLE_ALIASES.fetch(style, style)
86
+ end
87
+
88
+ def resolve_coordinates_mapbox(params, token, site)
89
+ return [params["lat"].to_f, params["lng"].to_f] if params["lat"] && params["lng"]
90
+ return geocode_mapbox(params["address"], token, site) if params["address"]
91
+ [nil, nil]
92
+ end
93
+
94
+ def geocode_mapbox(address, token, site)
95
+ cache = load_cache(site)
96
+ return cache[address] if cache.key?(address)
97
+
98
+ encoded = URI.encode_www_form_component(address)
99
+ uri = URI.parse("#{format(MAPBOX_GEOCODE_BASE, encoded)}?access_token=#{token}&limit=1")
100
+ response = Net::HTTP.get_response(uri)
101
+ raise "jekyll-map: Mapbox geocoding failed (#{response.code}) for: #{address}" unless response.is_a?(Net::HTTPSuccess)
102
+
103
+ data = JSON.parse(response.body)
104
+ features = data["features"]
105
+ raise "jekyll-map: no Mapbox geocoding results for: #{address}" if features.nil? || features.empty?
106
+
107
+ coords = features.first["center"] # [lng, lat]
108
+ result = [coords[1].round(6), coords[0].round(6)]
109
+ save_cache(site, cache.merge(address => result))
110
+ result
111
+ end
112
+
113
+ def build_mapbox_url(lat, lng, zoom, width, height, marker, style, token)
114
+ base = format(MAPBOX_STATIC_BASE, style)
115
+ overlay = marker ? "pin-s+ff0000(#{lng},#{lat})" : "none"
116
+ if overlay == "none"
117
+ "#{base}/#{lng},#{lat},#{zoom}/#{width}x#{height}?access_token=#{token}"
118
+ else
119
+ "#{base}/#{overlay}/#{lng},#{lat},#{zoom}/#{width}x#{height}?access_token=#{token}"
120
+ end
121
+ end
122
+
123
+ # -------------------------------------------------------------------------
124
+ # Google
125
+ # -------------------------------------------------------------------------
126
+
127
+ def render_google(params, map_config, site, zoom, width, height, marker, alt)
128
+ google_config = map_config["google"] || {}
129
+ key = google_config["key"]
130
+ raise "jekyll-map: map.google.key is required in _config.yml" if key.nil? || key.strip.empty?
131
+
132
+ lat, lng = resolve_coordinates_google(params, key, site)
133
+ raise "jekyll-map: could not resolve coordinates from markup: #{@raw_markup}" if lat.nil?
134
+
135
+ maptype = resolve_google_maptype(params["style"] || google_config["style"] || "roadmap")
136
+ url = build_google_url(lat, lng, zoom, width, height, marker, maptype, key)
137
+
138
+ img_tag(url, alt, width, height)
139
+ end
140
+
141
+ def resolve_google_maptype(style)
142
+ GOOGLE_MAPTYPE_ALIASES.fetch(style, "roadmap")
143
+ end
144
+
145
+ def resolve_coordinates_google(params, key, site)
146
+ return [params["lat"].to_f, params["lng"].to_f] if params["lat"] && params["lng"]
147
+ return geocode_google(params["address"], key, site) if params["address"]
148
+ [nil, nil]
149
+ end
150
+
151
+ def geocode_google(address, key, site)
152
+ cache_key = "google:#{address}"
153
+ cache = load_cache(site)
154
+ return cache[cache_key] if cache.key?(cache_key)
155
+
156
+ uri = URI.parse("#{GOOGLE_GEOCODE_BASE}?address=#{URI.encode_www_form_component(address)}&key=#{key}")
157
+ response = Net::HTTP.get_response(uri)
158
+ raise "jekyll-map: Google geocoding failed (#{response.code}) for: #{address}" unless response.is_a?(Net::HTTPSuccess)
159
+
160
+ data = JSON.parse(response.body)
161
+ results = data["results"]
162
+ raise "jekyll-map: no Google geocoding results for: #{address}" if results.nil? || results.empty?
163
+
164
+ loc = results.first["geometry"]["location"]
165
+ result = [loc["lat"].round(6), loc["lng"].round(6)]
166
+ save_cache(site, cache.merge(cache_key => result))
167
+ result
168
+ end
169
+
170
+ def build_google_url(lat, lng, zoom, width, height, marker, maptype, key)
171
+ params = {
172
+ center: "#{lat},#{lng}",
173
+ zoom: zoom,
174
+ size: "#{width}x#{height}",
175
+ maptype: maptype,
176
+ key: key
177
+ }
178
+ params[:markers] = "color:red|#{lat},#{lng}" if marker
179
+ query = params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join("&")
180
+ "#{GOOGLE_STATIC_BASE}?#{query}"
181
+ end
182
+
183
+ # -------------------------------------------------------------------------
184
+ # Shared helpers
185
+ # -------------------------------------------------------------------------
186
+
187
+ def parse_markup(markup)
188
+ params = {}
189
+ rest = markup.dup
190
+
191
+ if rest =~ /\A["'](.+?)["']\s*(.*)\z/m
192
+ params["address"] = Regexp.last_match(1)
193
+ rest = Regexp.last_match(2)
194
+ end
195
+
196
+ rest.scan(/(\w+)=("([^"]*?)"|'([^']*?)'|(\S+))/) do |key, _full, dq, sq, bare|
197
+ params[key] = dq || sq || bare
198
+ end
199
+
200
+ params
201
+ end
202
+
203
+ def truthy?(val)
204
+ return true if val.nil?
205
+ !%w[false 0 no].include?(val.to_s.downcase)
206
+ end
207
+
208
+ def cache_path(site)
209
+ File.join(site.source, "_map_cache.json")
210
+ end
211
+
212
+ def load_cache(site)
213
+ path = cache_path(site)
214
+ return {} unless File.exist?(path)
215
+ JSON.parse(File.read(path))
216
+ rescue JSON::ParserError
217
+ {}
218
+ end
219
+
220
+ def save_cache(site, cache)
221
+ File.write(cache_path(site), JSON.pretty_generate(cache))
222
+ end
223
+
224
+ def img_tag(url, alt, width, height)
225
+ %(<img src="#{url}" alt="#{CGI.escapeHTML(alt)}" width="#{width}" height="#{height}" class="jekyll-map" loading="lazy">)
226
+ end
227
+ end
228
+ end
229
+
230
+ Liquid::Template.register_tag("map", Jekyll::MapTag)
@@ -0,0 +1,5 @@
1
+ module Jekyll
2
+ module Map
3
+ VERSION = "0.2.0"
4
+ end
5
+ end
data/lib/jekyll-map.rb ADDED
@@ -0,0 +1,3 @@
1
+ require "jekyll"
2
+ require_relative "jekyll-map/version"
3
+ require_relative "jekyll/map_tag"
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jekyll-map
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Jason Chance
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2026-08-20 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: jekyll
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '4.0'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '5.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '4.0'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '5.0'
32
+ description: jekyll-map adds a {% map %} Liquid tag to Jekyll sites. Pass a latitude/longitude
33
+ or an address string and get a responsive static map image with no JavaScript required.
34
+ Powered by the Mapbox Static Images API.
35
+ email:
36
+ - jason@jasonchance.com
37
+ executables: []
38
+ extensions: []
39
+ extra_rdoc_files: []
40
+ files:
41
+ - CHANGELOG.md
42
+ - LICENSE.txt
43
+ - README.md
44
+ - lib/jekyll-map.rb
45
+ - lib/jekyll-map/version.rb
46
+ - lib/jekyll/map_tag.rb
47
+ homepage: https://jasonchance.com/projects/jekyll-map/
48
+ licenses:
49
+ - MIT
50
+ metadata: {}
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: 2.7.0
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubygems_version: 3.6.2
66
+ specification_version: 4
67
+ summary: A Jekyll plugin for embedding static Mapbox maps with a simple Liquid tag.
68
+ test_files: []