nswtopo 3.0.1 → 3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/nswtopo +19 -4
- data/docs/contours.md +2 -0
- data/docs/relief.md +2 -3
- data/docs/spot-heights.md +2 -0
- data/lib/nswtopo/archive.rb +6 -3
- data/lib/nswtopo/chrome.rb +9 -6
- data/lib/nswtopo/commands/layers.rb +2 -2
- data/lib/nswtopo/config.rb +1 -0
- data/lib/nswtopo/formats/gemf.rb +1 -0
- data/lib/nswtopo/formats/kmz.rb +16 -10
- data/lib/nswtopo/formats/mbtiles.rb +1 -0
- data/lib/nswtopo/formats/pdf.rb +4 -3
- data/lib/nswtopo/formats/svg.rb +4 -4
- data/lib/nswtopo/formats/svgz.rb +1 -0
- data/lib/nswtopo/formats/zip.rb +5 -4
- data/lib/nswtopo/formats.rb +35 -36
- data/lib/nswtopo/geometry/r_tree.rb +24 -23
- data/lib/nswtopo/geometry/straight_skeleton/node.rb +4 -4
- data/lib/nswtopo/geometry/straight_skeleton/nodes.rb +51 -40
- data/lib/nswtopo/geometry/straight_skeleton/split.rb +2 -2
- data/lib/nswtopo/geometry/vector.rb +55 -49
- data/lib/nswtopo/geometry.rb +0 -5
- data/lib/nswtopo/gis/arcgis/layer/map.rb +11 -10
- data/lib/nswtopo/gis/arcgis/layer/query.rb +8 -10
- data/lib/nswtopo/gis/arcgis/layer.rb +7 -11
- data/lib/nswtopo/gis/dem.rb +3 -2
- data/lib/nswtopo/gis/gdal_glob.rb +3 -3
- data/lib/nswtopo/gis/geojson/collection.rb +59 -13
- data/lib/nswtopo/gis/geojson/line_string.rb +142 -1
- data/lib/nswtopo/gis/geojson/multi_line_string.rb +49 -7
- data/lib/nswtopo/gis/geojson/multi_point.rb +87 -0
- data/lib/nswtopo/gis/geojson/multi_polygon.rb +35 -23
- data/lib/nswtopo/gis/geojson/point.rb +16 -1
- data/lib/nswtopo/gis/geojson/polygon.rb +69 -7
- data/lib/nswtopo/gis/geojson.rb +92 -46
- data/lib/nswtopo/gis/projection.rb +5 -1
- data/lib/nswtopo/helpers/thread_pool.rb +39 -0
- data/lib/nswtopo/helpers.rb +44 -5
- data/lib/nswtopo/layer/arcgis_raster.rb +3 -3
- data/lib/nswtopo/layer/contour.rb +24 -26
- data/lib/nswtopo/layer/control.rb +5 -3
- data/lib/nswtopo/layer/declination.rb +14 -10
- data/lib/nswtopo/layer/feature.rb +5 -5
- data/lib/nswtopo/layer/grid.rb +19 -18
- data/lib/nswtopo/layer/labels/barriers.rb +23 -0
- data/lib/nswtopo/layer/labels/convex_hull.rb +12 -0
- data/lib/nswtopo/layer/labels/convex_hulls.rb +86 -0
- data/lib/nswtopo/layer/labels/label.rb +63 -0
- data/lib/nswtopo/layer/labels.rb +192 -315
- data/lib/nswtopo/layer/overlay.rb +11 -12
- data/lib/nswtopo/layer/raster.rb +1 -0
- data/lib/nswtopo/layer/relief.rb +6 -4
- data/lib/nswtopo/layer/spot.rb +11 -17
- data/lib/nswtopo/layer/{vector → vector_render}/cutout.rb +1 -1
- data/lib/nswtopo/layer/{vector → vector_render}/knockout.rb +2 -3
- data/lib/nswtopo/layer/{vector.rb → vector_render.rb} +20 -45
- data/lib/nswtopo/layer.rb +2 -1
- data/lib/nswtopo/map.rb +56 -56
- data/lib/nswtopo/svg.rb +5 -0
- data/lib/nswtopo/tiled_web_map.rb +3 -3
- data/lib/nswtopo/tree_indenter.rb +2 -2
- data/lib/nswtopo/version.rb +1 -1
- data/lib/nswtopo.rb +4 -0
- metadata +15 -17
- data/lib/nswtopo/geometry/overlap.rb +0 -47
- data/lib/nswtopo/geometry/segment.rb +0 -27
- data/lib/nswtopo/geometry/vector_sequence.rb +0 -180
- data/lib/nswtopo/helpers/array.rb +0 -19
- data/lib/nswtopo/helpers/concurrently.rb +0 -27
- data/lib/nswtopo/helpers/dir.rb +0 -7
- data/lib/nswtopo/helpers/hash.rb +0 -15
- data/lib/nswtopo/helpers/tar_writer.rb +0 -11
- data/lib/nswtopo/layer/labels/barrier.rb +0 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c90485c22ed73dd281bbb6abb7dafd9ec919edda7065dd7106b0acd12485acf3
|
4
|
+
data.tar.gz: fc3bae27f7d28243a3e9e27e56c28c8c0a724ea92237c018e4b161c9ad1ee74a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 70779d0482c7d5350f65af71faa1503f3f290125e38bbe16e9017c10332da375e763ac4583907f43e741b612f928563379c32b7ec38652b22ffe2d2d34827519
|
7
|
+
data.tar.gz: d67a0b8e445cfec80c7ccae5d3df98983ec82b5533188e07e232f8dc41f825d70cc98026b5bb566127256eb22d4721e0aa1f697cccd80f98a85ee6cea8fdb006
|
data/bin/nswtopo
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
# Copyright 2011-
|
3
|
+
# Copyright 2011-2023 Matthew Hollingworth
|
4
4
|
#
|
5
5
|
# This program is free software: you can redistribute it and/or modify
|
6
6
|
# it under the terms of the GNU Affero General Public License as published by
|
@@ -37,8 +37,8 @@ begin
|
|
37
37
|
end
|
38
38
|
|
39
39
|
case
|
40
|
-
when (RUBY_VERSION.split(/\D+/).take(3).map(&:to_i) <=> [3,
|
41
|
-
log_abort "ruby 3.
|
40
|
+
when (RUBY_VERSION.split(/\D+/).take(3).map(&:to_i) <=> [3,1,4]) < 0
|
41
|
+
log_abort "ruby 3.1.4 or greater required"
|
42
42
|
when !Zlib.const_defined?(:GzipFile)
|
43
43
|
log_abort "ruby with GZIP_SUPPORT required"
|
44
44
|
when (GDAL_VERSION.split(/\D+/).take(3).map(&:to_i) <=> [3,4]) < 0
|
@@ -56,6 +56,7 @@ begin
|
|
56
56
|
NonNegFloat = Class.new
|
57
57
|
Dimensions = Class.new
|
58
58
|
Inset = Class.new
|
59
|
+
Radius = Class.new
|
59
60
|
Margins = Class.new
|
60
61
|
CoordList = Class.new
|
61
62
|
Rotation = Class.new
|
@@ -90,6 +91,13 @@ begin
|
|
90
91
|
string.split(?,).map(&:to_f).each_slice(4).entries
|
91
92
|
end
|
92
93
|
|
94
|
+
OptionParser.accept Radius, /\A#{float}(?:,#{digits})*\z/ do |string|
|
95
|
+
radius, segments = string.split(?,)
|
96
|
+
raise OptionParser::InvalidArgument, string unless radius.to_f.positive?
|
97
|
+
raise OptionParser::InvalidArgument, string if segments && segments.to_i.zero?
|
98
|
+
segments ? [radius.to_f, segments.to_i] : radius.to_f
|
99
|
+
end
|
100
|
+
|
93
101
|
OptionParser.accept Margins, /\A#{float}(?:,#{float})?\z/ do |string|
|
94
102
|
margins = string.split(?,).map(&:to_f)
|
95
103
|
raise OptionParser::InvalidArgument, string if margins.any?(&:negative?)
|
@@ -111,7 +119,7 @@ begin
|
|
111
119
|
end
|
112
120
|
|
113
121
|
OptionParser.accept Colour do |string|
|
114
|
-
string == "none" ? string : Colour.new(string.downcase)
|
122
|
+
string == "none" ? string : Colour.new(string.downcase).to_s
|
115
123
|
rescue Colour::Error
|
116
124
|
raise OptionParser::InvalidArgument, string
|
117
125
|
end
|
@@ -217,6 +225,7 @@ begin
|
|
217
225
|
"degrees, 'auto' or 'magnetic'"
|
218
226
|
parser.on "-m", "--margins <x[,y]>", Margins, "map margins in mm"
|
219
227
|
parser.on "-i", "--inset <x1,y1,x2,y2>", Inset, "map inset coordinates in mm"
|
228
|
+
parser.on "--radius <radius>", Radius, "map corner radius in mm"
|
220
229
|
parser.on "-o", "--overwrite", "overwrite existing map file"
|
221
230
|
|
222
231
|
when "info"
|
@@ -265,6 +274,7 @@ begin
|
|
265
274
|
parser.on "--stroke-width <width>", PositiveFloat, "stroke width in mm (default %s)" % NSWTopo::Contour::DEFAULTS["stroke-width"]
|
266
275
|
parser.on "--fill <colour>", Colour, "label colour (defaults to stroke colour)"
|
267
276
|
parser.on "-r", "--resolution <resolution>", PositiveFloat, "DEM processing resolution in metres"
|
277
|
+
parser.on "--epsg <epsg>", PositiveInt, "override EPSG projection code for DEM"
|
268
278
|
|
269
279
|
when "spot-heights"
|
270
280
|
parser.banner = <<~EOF
|
@@ -279,6 +289,7 @@ begin
|
|
279
289
|
parser.on "-b", "--before <layer>", "insert before specified layer"
|
280
290
|
parser.on "-c", "--replace <layer>", "replace specified layer"
|
281
291
|
parser.on "-r", "--resolution <resolution>", PositiveFloat, "DEM processing resolution in metres"
|
292
|
+
parser.on "--epsg <epsg>", PositiveInt, "override EPSG projection code for DEM"
|
282
293
|
|
283
294
|
when "relief"
|
284
295
|
parser.banner = <<~EOF
|
@@ -292,6 +303,7 @@ begin
|
|
292
303
|
parser.on "-m", "--method <igor|combined>", %w[igor combined], "relief shading method (default %s)" % NSWTopo::Relief::DEFAULTS["method"]
|
293
304
|
parser.on "-z", "--azimuth <azimuth>", Float, "azimuth in degrees (default %i)" % NSWTopo::Relief::DEFAULTS["azimuth"]
|
294
305
|
parser.on "-f", "--factor <factor>", PositiveFloat, "exaggeration factor (default %s)" % NSWTopo::Relief::DEFAULTS["factor"]
|
306
|
+
parser.on "--epsg <epsg>", PositiveInt, "override EPSG projection code for DEM"
|
295
307
|
|
296
308
|
when "grid"
|
297
309
|
parser.banner = <<~EOF
|
@@ -538,4 +550,7 @@ rescue Interrupt
|
|
538
550
|
log_abort "interrupted"
|
539
551
|
rescue RuntimeError => error
|
540
552
|
log_abort error.message
|
553
|
+
rescue StandardError
|
554
|
+
print "\r\e[2K" if $stdout.tty?
|
555
|
+
raise
|
541
556
|
end
|
data/docs/contours.md
CHANGED
@@ -14,6 +14,8 @@ Noise in raw elevation data usually produces unsuitably rough contour lines. Som
|
|
14
14
|
|
15
15
|
DEM tiles are normally processed at their maximumum native resolution. Change this using the `--resolution` option. A reduced resolution (say 5 metres) can markedly improve processing speed for 1- and 2-metre tiles.
|
16
16
|
|
17
|
+
Use the `--epsg` option to override the EPSG projection code for the DEM, in case of missing metadata (e.g. ACT 2015 DEM tiles).
|
18
|
+
|
17
19
|
# Layer Position
|
18
20
|
|
19
21
|
Use an `--after`, `--before` or `--replace` option to insert the contours in an appropriate layer position. You will most likely want to replace an existing contour layer:
|
data/docs/relief.md
CHANGED
@@ -14,9 +14,8 @@ No configuration is needed to get good results from ELVIS data. Use the followin
|
|
14
14
|
* **resolution**: resolution for the DEM data; a lower value will reduce file size but yields a smoother effect
|
15
15
|
* **opacity**: overall layer opacity
|
16
16
|
* **altitude**: raking angle of the light from the horizon
|
17
|
-
* **azimuth**: azimuth angle of the light, clockwise from north; deviation from the 315° default can be counter-intuitive
|
18
|
-
* **sources**: number of light sources to use for multi-directional shading
|
19
|
-
* **yellow**: amount of yellow illumination to apply as a fraction of grey shading
|
20
17
|
* **factor**: vertical exaggeration factor
|
21
18
|
|
22
19
|
Opacity and exaggeration can both be used to adjust the subtly of the shading effect.
|
20
|
+
|
21
|
+
Use the `--epsg` option to override the EPSG projection code for the DEM, in case of missing metadata (e.g. ACT 2015 DEM tiles).
|
data/docs/spot-heights.md
CHANGED
@@ -18,6 +18,8 @@ Use the `--extent` option to set a minimum size in millimetres when searching fo
|
|
18
18
|
|
19
19
|
DEM tiles are normally processed at their maximumum native resolution. Change this using the `--resolution` option. A reduced resolution (say 5 metres) can markedly improve processing speed for 1- and 2-metre tiles.
|
20
20
|
|
21
|
+
Use the `--epsg` option to override the EPSG projection code for the DEM, in case of missing metadata (e.g. ACT 2015 DEM tiles).
|
22
|
+
|
21
23
|
# Layer Position
|
22
24
|
|
23
25
|
Use an `--after`, `--before` or `--replace` option to insert the spot heights in an appropriate layer position. You will most likely want to replace an existing spot heights layer:
|
data/lib/nswtopo/archive.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
module NSWTopo
|
2
2
|
class Archive
|
3
|
+
using Helpers
|
3
4
|
extend Safely
|
4
5
|
include Enumerable
|
5
6
|
Invalid = Class.new RuntimeError
|
@@ -20,7 +21,7 @@ module NSWTopo
|
|
20
21
|
yield entry unless @entries.key? entry.full_name
|
21
22
|
end
|
22
23
|
@entries.each do |filename, entry|
|
23
|
-
yield entry if entry
|
24
|
+
yield entry.tap(&:rewind) if entry
|
24
25
|
end
|
25
26
|
end
|
26
27
|
|
@@ -71,8 +72,10 @@ module NSWTopo
|
|
71
72
|
end if in_path && false != Config["versioning"]
|
72
73
|
Gem::Package::TarReader.new(input) do |tar|
|
73
74
|
archive = new(tar).tap(&block)
|
74
|
-
|
75
|
-
|
75
|
+
archive.each do |entry|
|
76
|
+
buffer.write entry.header
|
77
|
+
buffer.write entry.read
|
78
|
+
buffer.write ?\0 while buffer.pos % 512 > 0
|
76
79
|
end if archive.changed?
|
77
80
|
end
|
78
81
|
end
|
data/lib/nswtopo/chrome.rb
CHANGED
@@ -77,10 +77,13 @@ module NSWTopo
|
|
77
77
|
*, status = Open3.capture2e *%W[taskkill /f /t /pid #{pid}]
|
78
78
|
Process.kill "KILL", pid unless status.success?
|
79
79
|
else
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
80
|
+
Process.kill "-USR1", Process.getpgid(pid)
|
81
|
+
Enumerator.produce(Time.now) do |time|
|
82
|
+
sleep 0.05 and Time.now
|
83
|
+
end.each.with_object(Time.now) do |time, start|
|
84
|
+
break true if Process.wait pid, Process::WNOHANG
|
85
|
+
break false if time - start > TIMEOUT_KILL
|
86
|
+
end or begin
|
84
87
|
Process.kill "-KILL", Process.getpgid(pid)
|
85
88
|
Process.wait pid
|
86
89
|
end
|
@@ -108,8 +111,8 @@ module NSWTopo
|
|
108
111
|
@id, @data_dir = 0, Dir.mktmpdir("nswtopo_headless_chrome_")
|
109
112
|
ObjectSpace.define_finalizer self, Chrome.rmdir(@data_dir)
|
110
113
|
|
111
|
-
args
|
112
|
-
args
|
114
|
+
args = [*args, "--disable-gpu"] if Config["gpu"] == false
|
115
|
+
args = [*args, "--user-data-dir=#{@data_dir}"]
|
113
116
|
|
114
117
|
input, @input, @output, output = *IO.pipe, *IO.pipe
|
115
118
|
input.nonblock, output.nonblock = false, false
|
@@ -6,7 +6,7 @@ module NSWTopo
|
|
6
6
|
log_warn "no layers installed" if paths.none?
|
7
7
|
|
8
8
|
TreeIndenter.new(paths) do |paths|
|
9
|
-
paths.
|
9
|
+
paths.filter_map do |path|
|
10
10
|
case
|
11
11
|
when path.glob("**/*.yml").any?
|
12
12
|
[path.basename.sub_ext(""), path.children.sort]
|
@@ -14,7 +14,7 @@ module NSWTopo
|
|
14
14
|
when path.extname == ".yml"
|
15
15
|
path.basename.sub_ext("")
|
16
16
|
end
|
17
|
-
end
|
17
|
+
end
|
18
18
|
end.each do |indents, name|
|
19
19
|
puts [*indents, name].join
|
20
20
|
end
|
data/lib/nswtopo/config.rb
CHANGED
data/lib/nswtopo/formats/gemf.rb
CHANGED
data/lib/nswtopo/formats/kmz.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module NSWTopo
|
2
2
|
module Formats
|
3
|
+
using Helpers
|
4
|
+
|
3
5
|
module Kmz
|
4
6
|
TILE_SIZE = 512
|
5
7
|
EARTH_RADIUS = 6378137.0
|
@@ -50,21 +52,26 @@ module NSWTopo
|
|
50
52
|
degree_resolution = 180.0 * metre_resolution / Math::PI / Kmz::EARTH_RADIUS
|
51
53
|
|
52
54
|
wgs84_bounds = @cutline.reproject_to_wgs84.bounds
|
53
|
-
wgs84_dimensions = wgs84_bounds.
|
55
|
+
wgs84_dimensions = wgs84_bounds.map do |min, max|
|
56
|
+
(max - min) / degree_resolution
|
57
|
+
end
|
54
58
|
|
55
59
|
max_zoom = Math::log2(wgs84_dimensions.max).ceil - Math::log2(Kmz::TILE_SIZE).to_i
|
56
60
|
png_path = yield(ppi: ppi)
|
57
61
|
|
58
62
|
Dir.mktmppath do |temp_dir|
|
63
|
+
log_update "kmz: resizing image pyramid"
|
59
64
|
pyramid = (0..max_zoom).map do |zoom|
|
60
65
|
resolution = degree_resolution * 2**(max_zoom - zoom)
|
61
|
-
degrees_per_tile = resolution * Kmz::TILE_SIZE
|
62
|
-
|
63
66
|
tif_path = temp_dir / "#{name}.kmz.zoom.#{zoom}.tif"
|
67
|
+
next zoom, resolution, tif_path
|
68
|
+
end.inject(ThreadPool.new, &:<<).each do |zoom, resolution, tif_path|
|
64
69
|
OS.gdalwarp "-t_srs", "EPSG:4326", "-tr", resolution, resolution, "-r", "bilinear", "-dstalpha", png_path, tif_path
|
65
|
-
|
70
|
+
end.map do |zoom, resolution, tif_path|
|
71
|
+
degrees_per_tile = resolution * Kmz::TILE_SIZE
|
66
72
|
corners = JSON.parse(OS.gdalinfo "-json", tif_path)["cornerCoordinates"]
|
67
73
|
top_left = corners["upperLeft"]
|
74
|
+
|
68
75
|
counts = corners.values.transpose.map(&:minmax).map do |min, max|
|
69
76
|
(max - min) / degrees_per_tile
|
70
77
|
end.map(&:ceil)
|
@@ -76,9 +83,8 @@ module NSWTopo
|
|
76
83
|
tile_bounds.each.with_index.entries
|
77
84
|
end.inject(:product).map(&:transpose).map(&:reverse).to_h
|
78
85
|
|
79
|
-
|
80
|
-
|
81
|
-
end.inject({}, &:merge)
|
86
|
+
next zoom, [indices_bounds, tif_path]
|
87
|
+
end.to_h
|
82
88
|
|
83
89
|
kmz_dir = temp_dir.join("#{name}.kmz").tap(&:mkpath)
|
84
90
|
pyramid.flat_map do |zoom, (indices_bounds, tif_path)|
|
@@ -117,9 +123,9 @@ module NSWTopo
|
|
117
123
|
end
|
118
124
|
end.tap do |tiles|
|
119
125
|
log_update "kmz: creating %i tiles" % tiles.length
|
120
|
-
end.
|
126
|
+
end.inject(ThreadPool.new, &:<<).each do |*args|
|
121
127
|
OS.gdal_translate "--config", "GDAL_PAM_ENABLED", "NO", *args
|
122
|
-
end.map(&:last).
|
128
|
+
end.map(&:last).inject(ThreadPool.new, &:<<).in_groups do |*tile_png_paths|
|
123
129
|
dither *tile_png_paths
|
124
130
|
rescue Dither::Missing
|
125
131
|
end
|
@@ -129,7 +135,7 @@ module NSWTopo
|
|
129
135
|
xml.add_element("kml", "xmlns" => "http://earth.google.com/kml/2.1").tap do |kml|
|
130
136
|
kml.add_element("Document").tap do |document|
|
131
137
|
document.add_element("LookAt").tap do |look_at|
|
132
|
-
extents = @dimensions.
|
138
|
+
extents = @dimensions.map { |dimension| dimension * @scale / 1000.0 }
|
133
139
|
range_x = extents.first / 2.0 / Math::tan(Kmz::FOV) / Math::cos(Kmz::TILT)
|
134
140
|
range_y = extents.last / Math::cos(Kmz::FOV - Kmz::TILT) / 2 / (Math::tan(Kmz::FOV - Kmz::TILT) + Math::sin(Kmz::TILT))
|
135
141
|
names_values = [%w[longitude latitude], @centre].transpose
|
data/lib/nswtopo/formats/pdf.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
module NSWTopo
|
2
2
|
module Formats
|
3
|
+
using Helpers
|
3
4
|
def render_pdf(pdf_path, ppi: nil, background:, **options)
|
4
5
|
if ppi
|
5
6
|
OS.gdal_translate "-of", "PDF", "-co", "DPI=#{ppi}", "-co", "MARGIN=0", "-co", "CREATOR=nswtopo", "-co", "GEO_ENCODING=ISO32000", yield(ppi: ppi), pdf_path
|
@@ -86,14 +87,14 @@ module NSWTopo
|
|
86
87
|
log_update "chrome: rendering PDF"
|
87
88
|
Chrome.with_browser("file://#{svg_path}") do |browser|
|
88
89
|
browser.print_to_pdf(pdf_path) do |doc|
|
89
|
-
bbox = [0, 0, *dimensions
|
90
|
+
bbox = [0, 0, dimensions[0] * 72 / 25.4, dimensions[1] * 72 / 25.4]
|
90
91
|
bounds = cutline.coordinates[0][...-1].map do |coords|
|
91
92
|
coords.zip(dimensions).map { |coord, dimension| coord / dimension }
|
92
93
|
end.flatten
|
93
94
|
lpts = [0, 0].zip( [1, 1]).inject(&:product).values_at(0,1,3,2).flatten
|
94
95
|
gpts = [0, 0].zip(dimensions).inject(&:product).values_at(0,1,3,2).then do |corners|
|
95
|
-
#
|
96
|
-
GeoJSON.multipoint(corners, projection: projection).reproject_to_wgs84.coordinates.map(&:reverse)
|
96
|
+
# ISO 32000-2 specifies projected coordinates instead of WGS84, but not observed in practice
|
97
|
+
GeoJSON.multipoint(corners, projection: projection).reproject_to_wgs84.coordinates.map(&:to_a).map(&:reverse)
|
97
98
|
end.flatten
|
98
99
|
pcsm = [25.4/72, 0, 0, 0, 25.4/72, 0, 0, 0, 1, 0, 0, 0]
|
99
100
|
|
data/lib/nswtopo/formats/svg.rb
CHANGED
@@ -78,15 +78,15 @@ module NSWTopo
|
|
78
78
|
layer.empty?
|
79
79
|
end.each do |layer|
|
80
80
|
next if Config["labelling"] == false
|
81
|
-
labels.add layer if
|
81
|
+
labels.add layer if VectorRender === layer
|
82
82
|
end.push(labels).each.with_object [[], []] do |layer, (cutouts, knockouts)|
|
83
83
|
log_update "compositing: #{layer.name}"
|
84
84
|
new_knockouts, knockout = [], "map.mask.knockout.#{knockouts.length+1}"
|
85
85
|
layer.render(cutouts: cutouts, knockout: knockout) do |object|
|
86
86
|
case object
|
87
|
-
when Labels::
|
88
|
-
when
|
89
|
-
when
|
87
|
+
when Labels::ConvexHulls then labels << object
|
88
|
+
when VectorRender::Cutout then cutouts << object
|
89
|
+
when VectorRender::Knockout then new_knockouts << object
|
90
90
|
when REXML::Element
|
91
91
|
object.attributes["mask"] ||= "url(#map.mask.knockout.#{knockouts.length})" unless "defs" == object.name
|
92
92
|
yielder << object
|
data/lib/nswtopo/formats/svgz.rb
CHANGED
data/lib/nswtopo/formats/zip.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
module NSWTopo
|
2
2
|
module Formats
|
3
|
+
using Helpers
|
3
4
|
def render_zip(zip_path, name:, ppi: PPI, **options)
|
4
5
|
Dir.mktmppath do |temp_dir|
|
5
6
|
zip_dir = temp_dir.join("zip").tap(&:mkpath)
|
@@ -8,7 +9,7 @@ module NSWTopo
|
|
8
9
|
|
9
10
|
2.downto(0).map.with_index do |level, index|
|
10
11
|
geo_transform = geotransform(ppi: ppi / 2**index)
|
11
|
-
outsize =
|
12
|
+
outsize = @dimensions.map { |dimension| (dimension / geo_transform[1]).ceil }
|
12
13
|
case index
|
13
14
|
when 0
|
14
15
|
thumb_size = outsize.inject(&:<) ? [0, 64] : [64, 0]
|
@@ -22,7 +23,7 @@ module NSWTopo
|
|
22
23
|
end
|
23
24
|
img_path = index.zero? ? png_path : temp_dir / "map.#{level}.png"
|
24
25
|
next level, outsize, img_path
|
25
|
-
end.
|
26
|
+
end.inject(ThreadPool.new, &:<<).each do |level, outsize, img_path|
|
26
27
|
OS.gdal_translate *%w[-r bicubic -outsize], *outsize, png_path, img_path unless img_path.exist?
|
27
28
|
end.flat_map do |level, outsize, img_path|
|
28
29
|
outsize.map do |px|
|
@@ -34,11 +35,11 @@ module NSWTopo
|
|
34
35
|
end
|
35
36
|
end.tap do |tiles|
|
36
37
|
log_update "zip: creating %i tiles" % tiles.length
|
37
|
-
end.
|
38
|
+
end.inject(ThreadPool.new, &:<<).each do |*args|
|
38
39
|
OS.gdal_translate *args
|
39
40
|
end.map(&:last).tap do |tile_paths|
|
40
41
|
log_update "zip: optimising %i tiles" % tile_paths.length
|
41
|
-
end.
|
42
|
+
end.inject(ThreadPool.new, &:<<).in_groups do |*tile_paths|
|
42
43
|
dither *tile_paths
|
43
44
|
rescue Dither::Missing
|
44
45
|
end
|
data/lib/nswtopo/formats.rb
CHANGED
@@ -8,10 +8,13 @@ require_relative 'formats/svgz'
|
|
8
8
|
|
9
9
|
module NSWTopo
|
10
10
|
module Formats
|
11
|
+
using Helpers
|
11
12
|
include Log
|
13
|
+
|
12
14
|
PPI = 300
|
13
15
|
TILE = 1500
|
14
|
-
|
16
|
+
CHROME_ARGS = %w[--force-gpu-mem-available-mb=4096]
|
17
|
+
CHROME_INSTANCES = (ThreadPool::CORES / 4).clamp(1, 6)
|
15
18
|
|
16
19
|
def self.extensions
|
17
20
|
instance_methods.grep(/^render_([a-z]+)/) { $1 }
|
@@ -69,48 +72,44 @@ module NSWTopo
|
|
69
72
|
end
|
70
73
|
|
71
74
|
viewport_size = [TILE * mm_per_px] * 2
|
72
|
-
raster_size =
|
75
|
+
raster_size = @dimensions.map { |dimension| (dimension / mm_per_px).ceil }
|
73
76
|
megapixels = raster_size.inject(&:*) / 1024.0 / 1024.0
|
74
77
|
|
75
78
|
raster_info = "%i×%i (%.1fMpx) map raster at %s" % [*raster_size, megapixels, ppi_info]
|
76
|
-
|
77
|
-
log_update chrome_message
|
79
|
+
log_update "chrome: creating #{raster_info}"
|
78
80
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
81
|
+
REXML::Document.new(svg_path.read).elements["svg/@viewBox"].value.split.map(&:to_f).last(2).map do |mm|
|
82
|
+
(0...(mm / mm_per_px).ceil).step(TILE).map do |px|
|
83
|
+
[px, px * mm_per_px]
|
84
|
+
end
|
85
|
+
end.inject(&:product).map(&:transpose).map do |raster_offset, viewport_offset|
|
86
|
+
next raster_offset, viewport_offset, temp_dir.join("tile.%i.%i.png" % raster_offset)
|
87
|
+
end.inject(ThreadPool.new(CHROME_INSTANCES), &:<<).in_groups do |*grid|
|
88
|
+
NSWTopo::Chrome.with_browser "file://#{svg_path}", width: TILE, height: TILE, args: CHROME_ARGS do |browser|
|
89
|
+
svg = browser.query_selector "svg"
|
90
|
+
svg[:width], svg[:height] = nil, nil
|
91
|
+
grid.each do |raster_offset, viewport_offset, tile_path|
|
92
|
+
svg[:viewBox] = [*viewport_offset, *viewport_size].join(?\s)
|
93
|
+
browser.screenshot tile_path
|
85
94
|
end
|
86
|
-
end
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
tile_path = temp_dir.join("tile.%i.%i.png" % raster_offset)
|
92
|
-
viewbox = [*viewport_offset, *viewport_size].join(?\s)
|
93
|
-
|
94
|
-
svg[:viewBox] = viewbox
|
95
|
-
browser.screenshot tile_path
|
96
|
-
|
97
|
-
REXML::Document.new(OS.gdal_translate "-of", "VRT", tile_path, "/vsistdout/").tap do |vrt|
|
98
|
-
vrt.elements.each("VRTDataset/VRTRasterBand/SimpleSource/DstRect") do |dst_rect|
|
99
|
-
dst_rect.add_attributes "xOff" => raster_offset[0], "yOff" => raster_offset[1]
|
100
|
-
end
|
95
|
+
end
|
96
|
+
end.map do |raster_offset, viewport_offset, tile_path|
|
97
|
+
REXML::Document.new(OS.gdal_translate "-of", "VRT", tile_path, "/vsistdout/").tap do |vrt|
|
98
|
+
vrt.elements.each("VRTDataset/VRTRasterBand/SimpleSource/DstRect") do |dst_rect|
|
99
|
+
dst_rect.add_attributes "xOff" => raster_offset[0], "yOff" => raster_offset[1]
|
101
100
|
end
|
102
|
-
end.inject do |vrt, tile_vrt|
|
103
|
-
vrt.elements["VRTDataset/VRTRasterBand[@band='1']"].add_element tile_vrt.elements["VRTDataset/VRTRasterBand[@band='1']/SimpleSource"]
|
104
|
-
vrt.elements["VRTDataset/VRTRasterBand[@band='2']"].add_element tile_vrt.elements["VRTDataset/VRTRasterBand[@band='2']/SimpleSource"]
|
105
|
-
vrt.elements["VRTDataset/VRTRasterBand[@band='3']"].add_element tile_vrt.elements["VRTDataset/VRTRasterBand[@band='3']/SimpleSource"]
|
106
|
-
vrt.elements["VRTDataset/VRTRasterBand[@band='4']"].add_element tile_vrt.elements["VRTDataset/VRTRasterBand[@band='4']/SimpleSource"]
|
107
|
-
vrt
|
108
|
-
end.tap do |vrt|
|
109
|
-
vrt.elements.each("VRTDataset/VRTRasterBand/@blockYSize", &:remove)
|
110
|
-
vrt.elements.each("VRTDataset/Metadata", &:remove)
|
111
|
-
vrt.elements["VRTDataset"].add_attributes "rasterXSize" => raster_size[0], "rasterYSize" => raster_size[1]
|
112
|
-
File.write vrt_path, vrt
|
113
101
|
end
|
102
|
+
end.inject do |vrt, tile_vrt|
|
103
|
+
vrt.elements["VRTDataset/VRTRasterBand[@band='1']"].add_element tile_vrt.elements["VRTDataset/VRTRasterBand[@band='1']/SimpleSource"]
|
104
|
+
vrt.elements["VRTDataset/VRTRasterBand[@band='2']"].add_element tile_vrt.elements["VRTDataset/VRTRasterBand[@band='2']/SimpleSource"]
|
105
|
+
vrt.elements["VRTDataset/VRTRasterBand[@band='3']"].add_element tile_vrt.elements["VRTDataset/VRTRasterBand[@band='3']/SimpleSource"]
|
106
|
+
vrt.elements["VRTDataset/VRTRasterBand[@band='4']"].add_element tile_vrt.elements["VRTDataset/VRTRasterBand[@band='4']/SimpleSource"]
|
107
|
+
vrt
|
108
|
+
end.tap do |vrt|
|
109
|
+
vrt.elements.each("VRTDataset/VRTRasterBand/@blockYSize", &:remove)
|
110
|
+
vrt.elements.each("VRTDataset/Metadata", &:remove)
|
111
|
+
vrt.elements["VRTDataset"].add_attributes "rasterXSize" => raster_size[0], "rasterYSize" => raster_size[1]
|
112
|
+
File.write vrt_path, vrt
|
114
113
|
end
|
115
114
|
|
116
115
|
log_update "nswtopo: finalising #{raster_info}"
|
@@ -1,46 +1,47 @@
|
|
1
1
|
class RTree
|
2
|
+
using Helpers
|
3
|
+
|
2
4
|
def initialize(nodes, bounds, object = nil)
|
3
5
|
@nodes, @bounds, @object = nodes, bounds, object
|
4
6
|
end
|
5
7
|
|
8
|
+
attr_reader :bounds
|
9
|
+
|
6
10
|
def overlaps?(bounds, buffer)
|
7
11
|
return false if @bounds.empty?
|
8
12
|
return true unless bounds
|
9
|
-
bounds.zip(@bounds).all? do |
|
10
|
-
|
11
|
-
limits.rotate(index).inject(&:-) <= buffer
|
12
|
-
end
|
13
|
+
bounds.zip(@bounds).all? do |(min1, max1), (min2, max2)|
|
14
|
+
max1 + buffer >= min2 && max2 + buffer >= min1
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
16
|
-
def self.load(
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
18
|
+
def self.load(objects, &bounds)
|
19
|
+
load! objects.map(&bounds).zip(objects)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.load!(bounds_objects, range = 0...bounds_objects.length)
|
23
|
+
return RTree.new([], *bounds_objects[range.begin]) if range.one?
|
24
|
+
bounds_objects.median_partition!(range) do |bounds, object|
|
25
|
+
bounds[0].sum
|
26
|
+
end.flat_map do |range|
|
27
|
+
bounds_objects.median_partition!(range) do |bounds, object|
|
28
|
+
bounds[1].sum
|
29
|
+
end
|
30
|
+
end.filter_map do |range|
|
31
|
+
load!(bounds_objects, range) if range.any?
|
32
|
+
end.then do |nodes|
|
33
|
+
RTree.new nodes, nodes.map(&:bounds).transpose.map(&:flatten).map(&:minmax)
|
31
34
|
end
|
32
35
|
end
|
33
36
|
|
34
|
-
def search(bounds, buffer
|
37
|
+
def search(bounds, buffer = 0)
|
35
38
|
Enumerator.new do |yielder|
|
36
|
-
next if searched.include? self
|
37
39
|
if overlaps? bounds, buffer
|
38
40
|
@nodes.each do |node|
|
39
|
-
node.search(bounds, buffer
|
41
|
+
node.search(bounds, buffer).each(&yielder)
|
40
42
|
end
|
41
43
|
yielder << @object if @nodes.empty?
|
42
44
|
end
|
43
|
-
searched << self
|
44
45
|
end
|
45
46
|
end
|
46
47
|
|
@@ -39,11 +39,11 @@ module StraightSkeleton
|
|
39
39
|
def project(travel)
|
40
40
|
det = normals.inject(&:cross) if normals.all?
|
41
41
|
case
|
42
|
-
when det
|
42
|
+
when det&.nonzero?
|
43
43
|
x = normals.map { |normal| travel - @travel + normal.dot(point) }
|
44
|
-
|
45
|
-
when normals[0] then normals[0]
|
46
|
-
when normals[1] then normals[1]
|
44
|
+
(normals[0].perp * x[1] - normals[1].perp * x[0]) / det
|
45
|
+
when normals[0] then normals[0] * (travel - @travel) + point
|
46
|
+
when normals[1] then normals[1] * (travel - @travel) + point
|
47
47
|
end
|
48
48
|
end
|
49
49
|
end
|