map_view 0.0.1a
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 +7 -0
- data/README.md +188 -0
- data/lib/gd/gis/basemap.rb +178 -0
- data/lib/gd/gis/classifier.rb +57 -0
- data/lib/gd/gis/color_helpers.rb +65 -0
- data/lib/gd/gis/crs_normalizer.rb +57 -0
- data/lib/gd/gis/feature.rb +153 -0
- data/lib/gd/gis/geometry.rb +235 -0
- data/lib/gd/gis/input/detector.rb +34 -0
- data/lib/gd/gis/input/geojson.rb +0 -0
- data/lib/gd/gis/input/kml.rb +0 -0
- data/lib/gd/gis/input/shapefile.rb +0 -0
- data/lib/gd/gis/layer_geojson.rb +66 -0
- data/lib/gd/gis/layer_lines.rb +44 -0
- data/lib/gd/gis/layer_points.rb +78 -0
- data/lib/gd/gis/layer_polygons.rb +54 -0
- data/lib/gd/gis/map.rb +370 -0
- data/lib/gd/gis/middleware.rb +89 -0
- data/lib/gd/gis/ontology.rb +26 -0
- data/lib/gd/gis/ontology.yml +28 -0
- data/lib/gd/gis/projection.rb +39 -0
- data/lib/gd/gis/style.rb +45 -0
- data/lib/gd/gis.rb +15 -0
- data/lib/libgd_gis.rb +44 -0
- metadata +87 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 68615351a014bd78c102aecfcfceab2c5b8f95fc40c5d002f7562753696eb047
|
|
4
|
+
data.tar.gz: 5041b59a1f06e55cf775ad52d42f9f86a205516ef6fe22515421af669efed106
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 8e2790b312928191a465fb42403126e69d567178946231659e68594e0d60b9cfad4ab61b246d9faa951699a2f47a09179d70d12df4908217bf48fde1109f39cd
|
|
7
|
+
data.tar.gz: b09a79564328702cb532d8cdea8e109a11f18d675c2e1e645d725a1147292d8d044cdad7c1586f05684040f265f61ec77f477298557e3be571a21059c8d7779b
|
data/README.md
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# LibGD-GIS
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<a href="https://rubystacknews.com/2026/01/07/ruby-can-now-draw-maps-and-i-started-with-ice-cream/">
|
|
5
|
+
<img src="https://img.shields.io/badge/RubyStackNews-CC342D?style=for-the-badge&logo=ruby&logoColor=white" />
|
|
6
|
+
</a>
|
|
7
|
+
<a href="https://x.com/ruby_stack_news">
|
|
8
|
+
<img src="https://img.shields.io/badge/Twitter%20@RubyStackNews-1DA1F2?style=for-the-badge&logo=twitter&logoColor=white" />
|
|
9
|
+
</a>
|
|
10
|
+
<a href="https://www.linkedin.com/in/germ%C3%A1n-silva-56a12622/">
|
|
11
|
+
<img src="https://img.shields.io/badge/Germán%20Silva-0A66C2?style=for-the-badge&logo=linkedin&logoColor=white" />
|
|
12
|
+
</a>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
<p align="center">
|
|
16
|
+
<a href="https://rubygems.org/gems/libgd-gis">
|
|
17
|
+
<img src="https://img.shields.io/badge/RubyGems-libgd--gis-CC342D?style=for-the-badge&logo=rubygems&logoColor=white" />
|
|
18
|
+
</a>
|
|
19
|
+
<a href="https://github.com/ggerman/libgd-gis">
|
|
20
|
+
<img src="https://img.shields.io/badge/GitHub-libgd--gis-181717?style=for-the-badge&logo=github&logoColor=white" />
|
|
21
|
+
</a>
|
|
22
|
+
<a href="https://github.com/ggerman/ruby-libgd">
|
|
23
|
+
<img src="https://img.shields.io/badge/Engine-ruby--libgd-CC342D?style=for-the-badge&logo=ruby&logoColor=white" />
|
|
24
|
+
</a>
|
|
25
|
+
</p>
|
|
26
|
+
|
|
27
|
+
<p align="right">
|
|
28
|
+
<img src="docs/images/logo-gis.png" width="160" />
|
|
29
|
+
</p>
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
[](https://www.codacy.com/app/libgd-gis/libgd-gis?utm_source=github.com&utm_medium=referral&utm_content=libgd-gis/libgd-gis&utm_campaign=Badge_Grade)
|
|
33
|
+
[](https://coveralls.io/github/libgd-gis/libgd-gis?branch=master)
|
|
34
|
+
[](https://rubygems.org/gems/libgd-gis)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
| Examples | Examples | Examples |
|
|
38
|
+
| :----: | :----: | :--: |
|
|
39
|
+
| <img src="docs/examples/parana.png" height="250"> | <img src="docs/examples/nyc.png" height="250"> | <img src="docs/examples/paris.png" height="250"> |
|
|
40
|
+
| <img src="examples/nyc/nyc.png" height="250"> | <img src="docs/examples/tokyo_solarized.png" height="250"> | <img src="examples/parana/parana.png" height="250"> |
|
|
41
|
+
| <img src="docs/examples/america.png" height="250"> | <img src="docs/examples/argentina_museum.png" height="250"> | <img src="docs/examples/museos_parana.png" height="250"> |
|
|
42
|
+
| <img src="docs/examples/asia.png" height="250"> | <img src="docs/examples/europe.png" height="250"> | <img src="docs/examples/icecream_parana.png" height="250"> |
|
|
43
|
+
| <img src="docs/examples/argentina_cities.png" height="250"> | <img src="docs/examples/tanzania_hydro.png" height="250"> | <img src="docs/examples/parana_polygon.png" height="250"> |
|
|
44
|
+
| <img src="docs/examples/parana_carto_dark.png" height="250"> | <img src="docs/examples/ramirez_avenue.png" height="250"> | <img src="examples/paris/paris.png" height="250"> |
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
> **libgd-gis is evolving very fast**, so some examples may temporarily stop working.
|
|
49
|
+
> Please report issues or ask for help — feedback is very welcome.
|
|
50
|
+
> https://github.com/ggerman/libgd-gis/issues or ggerman@gmail.com
|
|
51
|
+
|
|
52
|
+
--
|
|
53
|
+
|
|
54
|
+
## A geospatial raster engine for Ruby.
|
|
55
|
+
|
|
56
|
+
libgd-gis allows Ruby to render real maps, GeoJSON layers, vector features, and geospatial tiles using a native raster backend powered by **libgd**.
|
|
57
|
+
|
|
58
|
+
It restores something Ruby lost over time:
|
|
59
|
+
the ability to generate **maps, tiles, and GIS-grade visualizations natively**, without relying on external tools like QGIS, Mapnik, ImageMagick, or Mapbox.
|
|
60
|
+
|
|
61
|
+
Built on top of **ruby-libgd**, this project turns Ruby into a **map rendering engine**, capable of producing spatial graphics, tiled maps, and geospatial outputs directly inside Ruby processes.
|
|
62
|
+
|
|
63
|
+
- No external renderers.
|
|
64
|
+
- No shelling out.
|
|
65
|
+
- Just Ruby, raster, and GIS.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## What is this?
|
|
70
|
+
|
|
71
|
+
`libgd-gis` is a **geospatial rendering engine** for Ruby built on top of [`ruby-libgd`](https://github.com/ggerman/ruby-libgd).
|
|
72
|
+
|
|
73
|
+
It allows you to:
|
|
74
|
+
|
|
75
|
+
- Load GeoJSON, CSV, or any dataset with coordinates
|
|
76
|
+
- Fetch real basemap tiles
|
|
77
|
+
- Reproject WGS84 (lat/lon) into Web Mercator
|
|
78
|
+
- Render points, icons, and layers onto a raster map
|
|
79
|
+
- Generate PNG maps or map tiles
|
|
80
|
+
|
|
81
|
+
This is the same type of pipeline used by professional GIS systems — implemented in Ruby.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Installation
|
|
86
|
+
|
|
87
|
+
### System dependency
|
|
88
|
+
|
|
89
|
+
`libgd-gis` depends on **libgd**, via `ruby-libgd`.
|
|
90
|
+
|
|
91
|
+
Install libgd first:
|
|
92
|
+
|
|
93
|
+
**Ubuntu / Debian**
|
|
94
|
+
```
|
|
95
|
+
sudo apt install libgd-dev
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**macOS**
|
|
99
|
+
```
|
|
100
|
+
brew install gd
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
### Ruby gems
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
gem install ruby-libgd
|
|
109
|
+
gem install libgd-gis
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Quick Example
|
|
115
|
+
|
|
116
|
+
Render hydroelectric plants from a GeoJSON file:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
require "json"
|
|
120
|
+
require "gd/gis"
|
|
121
|
+
|
|
122
|
+
# ---------------------------
|
|
123
|
+
# Bounding box mundial
|
|
124
|
+
# ---------------------------
|
|
125
|
+
AMERICA = [-170, -60, -30, 75]
|
|
126
|
+
|
|
127
|
+
# ---------------------------
|
|
128
|
+
# Crear mapa
|
|
129
|
+
# ---------------------------
|
|
130
|
+
map = GD::GIS::Map.new(
|
|
131
|
+
bbox: AMERICA,
|
|
132
|
+
zoom: 4,
|
|
133
|
+
basemap: :carto_light
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Cargar datos
|
|
137
|
+
# ---------------------------
|
|
138
|
+
peaks = JSON.parse(File.read("picks.json"))
|
|
139
|
+
|
|
140
|
+
# ---------------------------
|
|
141
|
+
# Agregar capa de puntos
|
|
142
|
+
# ---------------------------
|
|
143
|
+
map.add_points(
|
|
144
|
+
peaks,
|
|
145
|
+
lon: ->(p) { p["longitude"] },
|
|
146
|
+
lat: ->(p) { p["latitude"] },
|
|
147
|
+
icon: "peak.png",
|
|
148
|
+
label: ->(p) { p["name"] },
|
|
149
|
+
font: "./fonts/DejaVuSans.ttf",
|
|
150
|
+
size: 10,
|
|
151
|
+
color: [0,0,0]
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# ---------------------------
|
|
155
|
+
# Renderizar y guardar
|
|
156
|
+
# ---------------------------
|
|
157
|
+
map.render
|
|
158
|
+
map.save("output/america.png")
|
|
159
|
+
|
|
160
|
+
puts "Saved output/america.png"
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Features
|
|
167
|
+
|
|
168
|
+
- Real basemap tiles
|
|
169
|
+
- WGS84 → Web Mercator projection
|
|
170
|
+
- GeoJSON point rendering
|
|
171
|
+
- CSV / JSON support
|
|
172
|
+
- Icon-based symbol layers
|
|
173
|
+
- Automatic bounding box fitting
|
|
174
|
+
- Raster output (PNG)
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
MIT
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Author
|
|
185
|
+
|
|
186
|
+
Germán Silva
|
|
187
|
+
https://github.com/ggerman
|
|
188
|
+
https://rubystacknews.com
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
require "net/http"
|
|
2
|
+
require "fileutils"
|
|
3
|
+
|
|
4
|
+
module GD
|
|
5
|
+
module GIS
|
|
6
|
+
class Basemap
|
|
7
|
+
TILE_SIZE = 256
|
|
8
|
+
attr_reader :origin_x, :origin_y
|
|
9
|
+
|
|
10
|
+
def initialize(zoom, bbox, provider=:carto_light)
|
|
11
|
+
@zoom = zoom
|
|
12
|
+
@bbox = bbox
|
|
13
|
+
@provider = provider
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def url(z, x, y, style = :osm)
|
|
17
|
+
case style
|
|
18
|
+
|
|
19
|
+
# ==============================
|
|
20
|
+
# OpenStreetMap
|
|
21
|
+
# ==============================
|
|
22
|
+
when :osm
|
|
23
|
+
"https://tile.openstreetmap.org/#{z}/#{x}/#{y}.png"
|
|
24
|
+
|
|
25
|
+
when :osm_hot
|
|
26
|
+
"https://tile.openstreetmap.fr/hot/#{z}/#{x}/#{y}.png"
|
|
27
|
+
|
|
28
|
+
when :osm_fr
|
|
29
|
+
"https://a.tile.openstreetmap.fr/osmfr/#{z}/#{x}/#{y}.png"
|
|
30
|
+
|
|
31
|
+
# ==============================
|
|
32
|
+
# CARTO
|
|
33
|
+
# ==============================
|
|
34
|
+
when :carto_light
|
|
35
|
+
"https://a.basemaps.cartocdn.com/light_all/#{z}/#{x}/#{y}.png"
|
|
36
|
+
|
|
37
|
+
when :carto_light_nolabels
|
|
38
|
+
"https://a.basemaps.cartocdn.com/light_nolabels/#{z}/#{x}/#{y}.png"
|
|
39
|
+
|
|
40
|
+
when :carto_dark
|
|
41
|
+
"https://a.basemaps.cartocdn.com/dark_all/#{z}/#{x}/#{y}.png"
|
|
42
|
+
|
|
43
|
+
when :carto_dark_nolabels
|
|
44
|
+
"https://a.basemaps.cartocdn.com/dark_nolabels/#{z}/#{x}/#{y}.png"
|
|
45
|
+
|
|
46
|
+
# ==============================
|
|
47
|
+
# ESRI / ArcGIS (Satellite, terrain, hybrid)
|
|
48
|
+
# ==============================
|
|
49
|
+
when :esri_satellite
|
|
50
|
+
"https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/#{z}/#{y}/#{x}"
|
|
51
|
+
|
|
52
|
+
when :esri_streets
|
|
53
|
+
"https://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/#{z}/#{y}/#{x}"
|
|
54
|
+
|
|
55
|
+
when :esri_terrain
|
|
56
|
+
"https://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/#{z}/#{y}/#{x}"
|
|
57
|
+
|
|
58
|
+
when :esri_hybrid
|
|
59
|
+
"https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/#{z}/#{y}/#{x}"
|
|
60
|
+
|
|
61
|
+
# ==============================
|
|
62
|
+
# STAMEN 503
|
|
63
|
+
# ==============================
|
|
64
|
+
when :stamen_toner
|
|
65
|
+
"https://stamen-tiles.a.ssl.fastly.net/toner/#{z}/#{x}/#{y}.png"
|
|
66
|
+
|
|
67
|
+
when :stamen_toner_lite
|
|
68
|
+
"https://stamen-tiles.a.ssl.fastly.net/toner-lite/#{z}/#{x}/#{y}.png"
|
|
69
|
+
|
|
70
|
+
when :stamen_terrain
|
|
71
|
+
"https://stamen-tiles.a.ssl.fastly.net/terrain/#{z}/#{x}/#{y}.png"
|
|
72
|
+
|
|
73
|
+
when :stamen_watercolor
|
|
74
|
+
"https://stamen-tiles.a.ssl.fastly.net/watercolor/#{z}/#{x}/#{y}.jpg"
|
|
75
|
+
|
|
76
|
+
# ==============================
|
|
77
|
+
# OpenTopoMap
|
|
78
|
+
# ==============================
|
|
79
|
+
when :topo
|
|
80
|
+
"https://a.tile.opentopomap.org/#{z}/#{x}/#{y}.png"
|
|
81
|
+
|
|
82
|
+
# ==============================
|
|
83
|
+
# Wikimedia 403
|
|
84
|
+
# ==============================
|
|
85
|
+
when :wikimedia
|
|
86
|
+
"https://maps.wikimedia.org/osm-intl/#{z}/#{x}/#{y}.png"
|
|
87
|
+
|
|
88
|
+
# ==============================
|
|
89
|
+
# OpenRailwayMap
|
|
90
|
+
# ==============================
|
|
91
|
+
when :railway
|
|
92
|
+
"https://tiles.openrailwaymap.org/standard/#{z}/#{x}/#{y}.png"
|
|
93
|
+
|
|
94
|
+
# ==============================
|
|
95
|
+
# CyclOSM
|
|
96
|
+
# ==============================
|
|
97
|
+
when :cyclosm
|
|
98
|
+
"https://a.tile-cyclosm.openstreetmap.fr/cyclosm/#{z}/#{x}/#{y}.png"
|
|
99
|
+
|
|
100
|
+
else
|
|
101
|
+
raise "Unknown basemap style: #{style}"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def lon2tile(lon)
|
|
106
|
+
((lon + 180.0) / 360.0 * (2 ** @zoom)).floor
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def lat2tile(lat)
|
|
110
|
+
rad = lat * Math::PI / 180
|
|
111
|
+
((1 - Math.log(Math.tan(rad) + 1 / Math.cos(rad)) / Math::PI) / 2 * (2 ** @zoom)).floor
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def fetch_tiles
|
|
115
|
+
west, south, east, north = @bbox
|
|
116
|
+
|
|
117
|
+
x_min = lon2tile(west)
|
|
118
|
+
x_max = lon2tile(east)
|
|
119
|
+
y_min = lat2tile(north)
|
|
120
|
+
y_max = lat2tile(south)
|
|
121
|
+
|
|
122
|
+
@x_min = x_min
|
|
123
|
+
@y_min = y_min
|
|
124
|
+
|
|
125
|
+
@origin_x = x_min * TILE_SIZE
|
|
126
|
+
@origin_y = y_min * TILE_SIZE
|
|
127
|
+
|
|
128
|
+
FileUtils.mkdir_p("tmp/tiles")
|
|
129
|
+
|
|
130
|
+
tiles = []
|
|
131
|
+
|
|
132
|
+
(x_min..x_max).each do |x|
|
|
133
|
+
(y_min..y_max).each do |y|
|
|
134
|
+
path = nil
|
|
135
|
+
|
|
136
|
+
unless File.exist?("tmp/tiles/#{@provider}_#{@zoom}_#{x}_#{y}.png") ||
|
|
137
|
+
File.exist?("tmp/tiles/#{@provider}_#{@zoom}_#{x}_#{y}.jpg")
|
|
138
|
+
|
|
139
|
+
uri = URI(url(@zoom, x, y, @provider))
|
|
140
|
+
|
|
141
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
|
|
142
|
+
req = Net::HTTP::Get.new(uri)
|
|
143
|
+
req["User-Agent"] = "libgd-gis/0.1 (Ruby)"
|
|
144
|
+
|
|
145
|
+
res = http.request(req)
|
|
146
|
+
raise "Tile fetch failed #{res.code}" unless res.code == "200"
|
|
147
|
+
|
|
148
|
+
content_type = res["content-type"]
|
|
149
|
+
|
|
150
|
+
ext =
|
|
151
|
+
if content_type&.include?("png")
|
|
152
|
+
"png"
|
|
153
|
+
elsif content_type&.include?("jpeg") || content_type&.include?("jpg")
|
|
154
|
+
"jpg"
|
|
155
|
+
else
|
|
156
|
+
raise "Unsupported tile type: #{content_type}"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
path = "tmp/tiles/#{@provider}_#{@zoom}_#{x}_#{y}.#{ext}"
|
|
160
|
+
File.binwrite(path, res.body)
|
|
161
|
+
end
|
|
162
|
+
else
|
|
163
|
+
if File.exist?("tmp/tiles/#{@provider}_#{@zoom}_#{x}_#{y}.png")
|
|
164
|
+
path = "tmp/tiles/#{@provider}_#{@zoom}_#{x}_#{y}.png"
|
|
165
|
+
else
|
|
166
|
+
path = "tmp/tiles/#{@provider}_#{@zoom}_#{x}_#{y}.jpg"
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
tiles << [x,y,path]
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
[tiles, x_min, y_min]
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module GD
|
|
2
|
+
module GIS
|
|
3
|
+
class Classifier
|
|
4
|
+
def self.road(feature)
|
|
5
|
+
tags = feature.properties || {}
|
|
6
|
+
|
|
7
|
+
case tags["highway"]
|
|
8
|
+
when "motorway", "trunk"
|
|
9
|
+
:motorway
|
|
10
|
+
when "primary", "primary_link"
|
|
11
|
+
:primary
|
|
12
|
+
when "secondary", "secondary_link"
|
|
13
|
+
:secondary
|
|
14
|
+
when "tertiary"
|
|
15
|
+
:street
|
|
16
|
+
when "residential", "living_street"
|
|
17
|
+
:street
|
|
18
|
+
when "service", "track"
|
|
19
|
+
:minor
|
|
20
|
+
else
|
|
21
|
+
nil
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.water?(feature)
|
|
26
|
+
p = feature.properties
|
|
27
|
+
|
|
28
|
+
p["waterway"] ||
|
|
29
|
+
p["natural"] == "water" ||
|
|
30
|
+
p["fclass"] == "river" ||
|
|
31
|
+
p["fclass"] == "stream"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.rail?(feature)
|
|
35
|
+
tags = feature.properties || {}
|
|
36
|
+
tags["railway"]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.park?(feature)
|
|
40
|
+
tags = feature.properties || {}
|
|
41
|
+
%w[park recreation_ground garden].include?(tags["leisure"]) ||
|
|
42
|
+
%w[park grass forest].include?(tags["landuse"])
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.water_kind(feature)
|
|
46
|
+
p = feature.properties
|
|
47
|
+
|
|
48
|
+
case p["waterway"] || p["fclass"]
|
|
49
|
+
when "river" then :river
|
|
50
|
+
when "stream" then :stream
|
|
51
|
+
else :minor
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module GD
|
|
2
|
+
module GIS
|
|
3
|
+
module ColorHelpers
|
|
4
|
+
# --------------------------------------------------
|
|
5
|
+
# Random RGB color
|
|
6
|
+
# --------------------------------------------------
|
|
7
|
+
def self.random_rgb(min: 0, max: 255)
|
|
8
|
+
GD::Color.rgb(
|
|
9
|
+
rand(min..max),
|
|
10
|
+
rand(min..max),
|
|
11
|
+
rand(min..max)
|
|
12
|
+
)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# --------------------------------------------------
|
|
16
|
+
# Random RGBA color
|
|
17
|
+
# --------------------------------------------------
|
|
18
|
+
def self.random_rgba(min: 0, max: 255, alpha: nil)
|
|
19
|
+
GD::Color.rgba(
|
|
20
|
+
rand(min..max),
|
|
21
|
+
rand(min..max),
|
|
22
|
+
rand(min..max),
|
|
23
|
+
alpha || rand(50..255)
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# --------------------------------------------------
|
|
28
|
+
# Random vivid color (avoid gray/mud)
|
|
29
|
+
# --------------------------------------------------
|
|
30
|
+
def self.random_vivid
|
|
31
|
+
h = rand
|
|
32
|
+
s = rand(0.6..1.0)
|
|
33
|
+
v = rand(0.7..1.0)
|
|
34
|
+
|
|
35
|
+
r, g, b = hsv_to_rgb(h, s, v)
|
|
36
|
+
GD::Color.rgb(r, g, b)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# --------------------------------------------------
|
|
40
|
+
# HSV → RGB
|
|
41
|
+
# --------------------------------------------------
|
|
42
|
+
def self.hsv_to_rgb(h, s, v)
|
|
43
|
+
i = (h * 6).floor
|
|
44
|
+
f = h * 6 - i
|
|
45
|
+
p = v * (1 - s)
|
|
46
|
+
q = v * (1 - f * s)
|
|
47
|
+
t = v * (1 - (1 - f) * s)
|
|
48
|
+
|
|
49
|
+
r, g, b =
|
|
50
|
+
case i % 6
|
|
51
|
+
when 0 then [v, t, p]
|
|
52
|
+
when 1 then [q, v, p]
|
|
53
|
+
when 2 then [p, v, t]
|
|
54
|
+
when 3 then [p, q, v]
|
|
55
|
+
when 4 then [t, p, v]
|
|
56
|
+
when 5 then [v, p, q]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
[(r * 255).to_i, (g * 255).to_i, (b * 255).to_i]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module GD
|
|
2
|
+
module GIS
|
|
3
|
+
module CRS
|
|
4
|
+
CRS84 = "urn:ogc:def:crs:OGC:1.3:CRS84"
|
|
5
|
+
EPSG4326 = "EPSG:4326"
|
|
6
|
+
EPSG3857 = "EPSG:3857"
|
|
7
|
+
|
|
8
|
+
class Normalizer
|
|
9
|
+
def initialize(crs)
|
|
10
|
+
@crs = normalize_name(crs)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Accepts:
|
|
14
|
+
# normalize(lon,lat)
|
|
15
|
+
# normalize(lon,lat,z)
|
|
16
|
+
# normalize([lon,lat])
|
|
17
|
+
# normalize([lon,lat,z])
|
|
18
|
+
def normalize(*args)
|
|
19
|
+
lon, lat = args.flatten
|
|
20
|
+
return nil if lon.nil? || lat.nil?
|
|
21
|
+
|
|
22
|
+
lon = lon.to_f
|
|
23
|
+
lat = lat.to_f
|
|
24
|
+
|
|
25
|
+
case @crs
|
|
26
|
+
when CRS84, nil
|
|
27
|
+
[lon, lat]
|
|
28
|
+
|
|
29
|
+
when EPSG4326
|
|
30
|
+
# axis order lat,lon → lon,lat
|
|
31
|
+
[lat, lon]
|
|
32
|
+
|
|
33
|
+
when EPSG3857
|
|
34
|
+
mercator_to_wgs84(lon, lat)
|
|
35
|
+
|
|
36
|
+
else
|
|
37
|
+
[lon, lat]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def normalize_name(name)
|
|
44
|
+
return nil if name.nil?
|
|
45
|
+
name.to_s.strip
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def mercator_to_wgs84(x, y)
|
|
49
|
+
r = 6378137.0
|
|
50
|
+
lon = (x / r) * 180.0 / Math::PI
|
|
51
|
+
lat = (2 * Math.atan(Math.exp(y / r)) - Math::PI / 2) * 180.0 / Math::PI
|
|
52
|
+
[lon, lat]
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|