mapkick-static 0.1.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: 1a8fd5cb74990e96209e87feab8beee6b31e1d69a42f34a10a92beb03cd5e064
4
+ data.tar.gz: d18aa7816d2e76a2cead6a487d443b88c3876d28f3885b578318e03d9c87332b
5
+ SHA512:
6
+ metadata.gz: 35dd8ef3064bc945f515119fee0fa29e0f58aad2e2042a5a23e407b6fa5cc10bdc6b5c109c61196c59fd7c71cb95f5e37aabc3bfc7e49101fccdb52bddfaac8e
7
+ data.tar.gz: e0e8577a9d1e7dd8cc6dc5b55e125f84aa03d5f0a61cda7068608cc807b12bfc7e2729d09d947e211558171d8c54a3567f0afeb1fac9fcd20441706c87064989
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## 0.1.0 (2023-04-26)
2
+
3
+ - First commit
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Andrew Kane
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # Mapkick Static
2
+
3
+ Create beautiful static maps with one line of Ruby. No more fighting with mapping libraries!
4
+
5
+ [See it in action](https://chartkick.com/mapkick-static)
6
+
7
+ :fire: For JavaScript maps, check out [Mapkick](https://chartkick.com/mapkick)
8
+
9
+ [![Build Status](https://github.com/ankane/mapkick-static/workflows/build/badge.svg?branch=master)](https://github.com/ankane/mapkick-static/actions)
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application’s Gemfile:
14
+
15
+ ```ruby
16
+ gem "mapkick-static"
17
+ ```
18
+
19
+ Mapkick Static uses the [Mapbox Static Images API](https://docs.mapbox.com/api/maps/static-images/). [Create a Mapbox account](https://account.mapbox.com/auth/signup/) to get an access token and set `ENV["MAPBOX_ACCESS_TOKEN"]` in your environment.
20
+
21
+ ## Maps
22
+
23
+ Point map
24
+
25
+ ```erb
26
+ <%= static_map [{latitude: 37.7829, longitude: -122.4190}] %>
27
+ ```
28
+
29
+ Area map (experimental)
30
+
31
+ ```erb
32
+ <%= static_area_map [{geometry: {type: "Polygon", coordinates: ...}}] %>
33
+ ```
34
+
35
+ ## Data
36
+
37
+ Data can be an array
38
+
39
+ ```erb
40
+ <%= static_map [{latitude: 37.7829, longitude: -122.4190}] %>
41
+ ```
42
+
43
+ ### Point Map
44
+
45
+ Use `latitude` or `lat` for latitude and `longitude`, `lon`, or `lng` for longitude
46
+
47
+ You can specify a color for each data point
48
+
49
+ ```ruby
50
+ {
51
+ latitude: ...,
52
+ longitude: ...,
53
+ color: "#f84d4d"
54
+ }
55
+ ```
56
+
57
+ ### Area Map
58
+
59
+ Use `geometry` with a GeoJSON `Polygon` or `MultiPolygon`
60
+
61
+ You can specify a color for each data point
62
+
63
+ ```ruby
64
+ {
65
+ geometry: {type: "Polygon", coordinates: ...},
66
+ color: "#0090ff"
67
+ }
68
+ ```
69
+
70
+ ## Options
71
+
72
+ Width and height
73
+
74
+ ```erb
75
+ <%= static_map data, width: 800, height: 500 %>
76
+ ```
77
+
78
+ Alt text
79
+
80
+ ```erb
81
+ <%= static_map data, alt: "Map of ..." %>
82
+ ```
83
+
84
+ Marker color
85
+
86
+ ```erb
87
+ <%= static_map data, markers: {color: "#f84d4d"} %>
88
+ ```
89
+
90
+ Map style
91
+
92
+ ```erb
93
+ <%= static_map data, style: "mapbox/outdoors-v12" %>
94
+ ```
95
+
96
+ ## History
97
+
98
+ View the [changelog](https://github.com/ankane/mapkick-static/blob/master/CHANGELOG.md)
99
+
100
+ ## Contributing
101
+
102
+ Everyone is encouraged to help improve this project. Here are a few ways you can help:
103
+
104
+ - [Report bugs](https://github.com/ankane/mapkick-static/issues)
105
+ - Fix bugs and [submit pull requests](https://github.com/ankane/mapkick-static/pulls)
106
+ - Write, clarify, or fix documentation
107
+ - Suggest or add new features
108
+
109
+ To get started with development:
110
+
111
+ ```sh
112
+ git clone https://github.com/ankane/mapkick-static.git
113
+ cd mapkick-static
114
+ bundle install
115
+ bundle exec rake test
116
+ ```
@@ -0,0 +1,27 @@
1
+ module Mapkick
2
+ module Static
3
+ class AreaMap < BaseMap
4
+ private
5
+
6
+ def generate_features(data, default_color)
7
+ default_color ||= "#0090ff"
8
+
9
+ data.map do |v|
10
+ color = v["color"] || default_color
11
+ {
12
+ type: "Feature",
13
+ # TODO round coordinates
14
+ geometry: v["geometry"],
15
+ properties: {
16
+ "fill" => color,
17
+ "fill-opacity" => 0.3,
18
+ "stroke" => color,
19
+ "stroke-width" => 1,
20
+ "stroke-opacity" => 0.7
21
+ }
22
+ }
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,99 @@
1
+ module Mapkick
2
+ module Static
3
+ class BaseMap
4
+ attr_reader :url, :url_2x
5
+
6
+ def initialize(data, width: 800, height: 500, markers: {}, style: "mapbox/streets-v12", alt: "Map", access_token: nil, view_context: nil)
7
+ @width = width.to_i
8
+ @height = height.to_i
9
+ @alt = alt
10
+ @view_context = view_context
11
+
12
+ prefix = "https://api.mapbox.com/styles/v1"
13
+ style = set_style(style)
14
+ geojson = create_geojson(data, markers)
15
+ overlay = "geojson(#{CGI.escape(JSON.generate(geojson))})"
16
+ viewport = set_viewport(geojson)
17
+ size = "%dx%d" % [@width.to_i, @height.to_i]
18
+ query = set_query(access_token, viewport)
19
+
20
+ url = "#{prefix}/#{style}/static/#{overlay}/#{viewport}/#{size}"
21
+ @url = "#{url}?#{query}"
22
+ @url_2x = "#{url}@2x?#{query}"
23
+
24
+ check_request_size
25
+ end
26
+
27
+ def to_s
28
+ @view_context.image_tag(url, alt: @alt, style: image_style, srcset: "#{url} 1x, #{url_2x} 2x")
29
+ end
30
+
31
+ private
32
+
33
+ def set_style(style)
34
+ style = style.delete_prefix("mapbox://styles/")
35
+ if style.count("/") != 1
36
+ raise ArgumentError, "Invalid style"
37
+ end
38
+ style.split("/", 2).map { |v| CGI.escape(v) }.join("/")
39
+ end
40
+
41
+ def create_geojson(data, markers)
42
+ data = data.map { |v| v.transform_keys(&:to_s) }
43
+ default_color = markers.transform_keys(&:to_s)["color"]
44
+ {
45
+ type: "FeatureCollection",
46
+ features: generate_features(data, default_color)
47
+ }
48
+ end
49
+
50
+ def set_viewport(geojson)
51
+ if geojson[:features].size == 1 && (geometry = geojson[:features][0][:geometry]) && geometry&.[](:type) == "MultiPoint" && geometry[:coordinates].size == 1
52
+ coordinates = geometry[:coordinates][0]
53
+ zoom = 15
54
+ "%f,%f,%d" % [round_coordinate(coordinates[0].to_f), round_coordinate(coordinates[1].to_f), zoom.to_i]
55
+ else
56
+ "auto"
57
+ end
58
+ end
59
+
60
+ def set_query(access_token, viewport)
61
+ params = {}
62
+ params[:access_token] = check_access_token(access_token || ENV["MAPBOX_ACCESS_TOKEN"])
63
+ if viewport == "auto"
64
+ params[:padding] = 40
65
+ end
66
+ URI.encode_www_form(params)
67
+ end
68
+
69
+ def check_access_token(access_token)
70
+ if !access_token
71
+ raise Error, "No access token"
72
+ elsif access_token.start_with?("sk.")
73
+ # can bypass with string keys
74
+ # but should help prevent common errors
75
+ raise Error, "Expected public access token"
76
+ elsif !access_token.start_with?("pk.")
77
+ raise Error, "Invalid access token"
78
+ end
79
+ access_token
80
+ end
81
+
82
+ # round to reduce URL size
83
+ def round_coordinate(point)
84
+ point.round(7)
85
+ end
86
+
87
+ # https://docs.mapbox.com/api/overview/#url-length-limits
88
+ def check_request_size
89
+ if @url_2x.bytesize > 8192
90
+ warn "[mapkick-static] URL exceeds 8192 byte limit of API (#{@url_2x.bytesize} bytes)"
91
+ end
92
+ end
93
+
94
+ def image_style
95
+ "width: %dpx; height: %dpx;" % [@width.to_i, @height.to_i]
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,13 @@
1
+ module Mapkick
2
+ module Static
3
+ module Helper
4
+ def static_map(data, **options)
5
+ Mapkick::Static::Map.new(data, **options, view_context: self)
6
+ end
7
+
8
+ def static_area_map(data, **options)
9
+ Mapkick::Static::AreaMap.new(data, **options, view_context: self)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,34 @@
1
+ module Mapkick
2
+ module Static
3
+ class Map < BaseMap
4
+ private
5
+
6
+ def generate_features(data, default_color)
7
+ default_color ||= "#f84d4d"
8
+ default_icon = nil
9
+
10
+ data.group_by { |v| [v["color"] || default_color, v["x_icon"] || default_icon] }.map do |(color, icon), vs|
11
+ geometry = {
12
+ type: "MultiPoint",
13
+ coordinates: vs.map { |v| row_coordinates(v).map { |vi| round_coordinate(vi) } }
14
+ }
15
+
16
+ properties = {
17
+ "marker-color" => color
18
+ }
19
+ properties["marker-symbol"] = icon if icon
20
+
21
+ {
22
+ type: "Feature",
23
+ geometry: geometry,
24
+ properties: properties
25
+ }
26
+ end
27
+ end
28
+
29
+ def row_coordinates(row)
30
+ [row["longitude"] || row["lng"] || row["lon"], row["latitude"] || row["lat"]]
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ module Mapkick
2
+ module Static
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,29 @@
1
+ # stdlib
2
+ require "cgi"
3
+ require "json"
4
+ require "uri"
5
+
6
+ # maps
7
+ require_relative "static/base_map"
8
+ require_relative "static/area_map"
9
+ require_relative "static/map"
10
+
11
+ # modules
12
+ require_relative "static/helper"
13
+ require_relative "static/version"
14
+
15
+ if defined?(ActiveSupport.on_load)
16
+ ActiveSupport.on_load(:action_view) do
17
+ include Mapkick::Static::Helper
18
+ end
19
+
20
+ ActiveSupport.on_load(:action_mailer) do
21
+ include Mapkick::Static::Helper
22
+ end
23
+ end
24
+
25
+ module Mapkick
26
+ module Static
27
+ class Error < StandardError; end
28
+ end
29
+ end
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mapkick-static
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Kane
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-04-26 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email: andrew@ankane.org
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - CHANGELOG.md
20
+ - LICENSE.txt
21
+ - README.md
22
+ - lib/mapkick/static.rb
23
+ - lib/mapkick/static/area_map.rb
24
+ - lib/mapkick/static/base_map.rb
25
+ - lib/mapkick/static/helper.rb
26
+ - lib/mapkick/static/map.rb
27
+ - lib/mapkick/static/version.rb
28
+ homepage: https://chartkick.com/mapkick-static
29
+ licenses:
30
+ - MIT
31
+ metadata: {}
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '3'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubygems_version: 3.4.10
48
+ signing_key:
49
+ specification_version: 4
50
+ summary: Create beautiful static maps with one line of Ruby
51
+ test_files: []