nswtopo 2.0.0 → 3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/COPYING +70 -83
- data/bin/nswtopo +227 -116
- data/docs/README.md +1 -12
- data/docs/add.md +1 -1
- data/docs/config.md +1 -1
- data/docs/contours.md +3 -1
- data/docs/init.md +8 -0
- data/docs/inspect.md +103 -0
- data/docs/move.md +9 -0
- data/docs/render.md +16 -7
- data/docs/scrape.md +67 -0
- data/docs/spot-heights.md +6 -2
- data/lib/nswtopo/archive.rb +50 -41
- data/lib/nswtopo/chrome.rb +232 -0
- data/lib/nswtopo/commands/add.rb +106 -0
- data/lib/nswtopo/commands/config.rb +38 -0
- data/lib/nswtopo/commands/inspect.rb +74 -0
- data/lib/nswtopo/commands/layers.rb +22 -0
- data/lib/nswtopo/commands/scrape.rb +79 -0
- data/lib/nswtopo/commands.rb +57 -0
- data/lib/nswtopo/dither.rb +5 -3
- data/lib/nswtopo/font.rb +46 -21
- data/lib/nswtopo/formats/gemf.rb +42 -0
- data/lib/nswtopo/formats/kmz.rb +26 -24
- data/lib/nswtopo/formats/mbtiles.rb +5 -41
- data/lib/nswtopo/formats/pdf.rb +112 -18
- data/lib/nswtopo/formats/svg.rb +114 -45
- data/lib/nswtopo/formats/svgz.rb +2 -2
- data/lib/nswtopo/formats/zip.rb +33 -23
- data/lib/nswtopo/formats.rb +77 -32
- data/lib/nswtopo/geometry/overlap.rb +1 -32
- data/lib/nswtopo/geometry/r_tree.rb +16 -10
- data/lib/nswtopo/geometry/segment.rb +3 -3
- data/lib/nswtopo/geometry/straight_skeleton/nodes.rb +5 -6
- data/lib/nswtopo/geometry/vector_sequence.rb +7 -6
- data/lib/nswtopo/gis/arcgis/connection.rb +56 -0
- data/lib/nswtopo/gis/arcgis/layer/map.rb +163 -0
- data/lib/nswtopo/gis/arcgis/layer/query.rb +87 -0
- data/lib/nswtopo/gis/arcgis/layer/renderer.rb +66 -0
- data/lib/nswtopo/gis/arcgis/layer/statistics.rb +15 -0
- data/lib/nswtopo/gis/arcgis/layer.rb +201 -0
- data/lib/nswtopo/gis/arcgis/service.rb +57 -0
- data/lib/nswtopo/gis/arcgis.rb +3 -0
- data/lib/nswtopo/gis/dem.rb +13 -12
- data/lib/nswtopo/gis/esri_hdr.rb +8 -2
- data/lib/nswtopo/gis/geojson/collection.rb +45 -21
- data/lib/nswtopo/gis/geojson/multi_line_string.rb +2 -24
- data/lib/nswtopo/gis/geojson/multi_polygon.rb +2 -53
- data/lib/nswtopo/gis/geojson/polygon.rb +15 -0
- data/lib/nswtopo/gis/geojson.rb +12 -3
- data/lib/nswtopo/gis/gps/kml.rb +25 -19
- data/lib/nswtopo/gis/gps.rb +2 -0
- data/lib/nswtopo/gis/projection.rb +35 -24
- data/lib/nswtopo/gis/shapefile.rb +89 -16
- data/lib/nswtopo/gis.rb +1 -2
- data/lib/nswtopo/helpers/array.rb +0 -11
- data/lib/nswtopo/helpers/colour.rb +34 -14
- data/lib/nswtopo/layer/arcgis_raster.rb +44 -48
- data/lib/nswtopo/layer/colour_mask.rb +5 -0
- data/lib/nswtopo/layer/contour.rb +35 -28
- data/lib/nswtopo/layer/control.rb +2 -7
- data/lib/nswtopo/layer/declination.rb +9 -9
- data/lib/nswtopo/layer/feature.rb +36 -22
- data/lib/nswtopo/layer/grid.rb +30 -27
- data/lib/nswtopo/layer/import.rb +1 -21
- data/lib/nswtopo/layer/labels/barrier.rb +39 -0
- data/lib/nswtopo/layer/labels.rb +551 -383
- data/lib/nswtopo/layer/mask_render.rb +37 -0
- data/lib/nswtopo/layer/overlay.rb +2 -2
- data/lib/nswtopo/layer/raster.rb +31 -41
- data/lib/nswtopo/layer/raster_import.rb +17 -0
- data/lib/nswtopo/layer/raster_render.rb +15 -0
- data/lib/nswtopo/layer/relief.rb +27 -95
- data/lib/nswtopo/layer/spot.rb +63 -62
- data/lib/nswtopo/layer/vector/cutout.rb +15 -0
- data/lib/nswtopo/layer/vector/knockout.rb +16 -0
- data/lib/nswtopo/layer/vector.rb +121 -89
- data/lib/nswtopo/layer/vegetation.rb +39 -34
- data/lib/nswtopo/layer.rb +30 -16
- data/lib/nswtopo/map.rb +204 -109
- data/lib/nswtopo/os.rb +5 -27
- data/lib/nswtopo/tiled_web_map.rb +54 -0
- data/lib/nswtopo/tree_indenter.rb +27 -0
- data/lib/nswtopo/version.rb +27 -2
- data/lib/nswtopo.rb +7 -196
- metadata +39 -20
- data/lib/nswtopo/font/chrome.rb +0 -59
- data/lib/nswtopo/font/generic.rb +0 -25
- data/lib/nswtopo/gis/arcgis_server/connection.rb +0 -52
- data/lib/nswtopo/gis/arcgis_server.rb +0 -155
- data/lib/nswtopo/gis/geojson/multi_point.rb +0 -12
- data/lib/nswtopo/gis/world_file.rb +0 -19
- data/lib/nswtopo/layer/labels/fence.rb +0 -20
data/lib/nswtopo/gis/gps/kml.rb
CHANGED
@@ -3,7 +3,9 @@ module NSWTopo
|
|
3
3
|
module KML
|
4
4
|
def styles(placemark)
|
5
5
|
style_url = placemark.elements["styleUrl"]&.text&.delete_prefix(?#)
|
6
|
-
|
6
|
+
return {} unless style_url
|
7
|
+
|
8
|
+
style_element = @xml.elements["/kml/Document/*[@id='%s']" % style_url]
|
7
9
|
result = {}
|
8
10
|
|
9
11
|
case style_element&.name
|
@@ -39,25 +41,29 @@ module NSWTopo
|
|
39
41
|
|
40
42
|
def collection
|
41
43
|
GeoJSON::Collection.new.tap do |collection|
|
42
|
-
@xml.elements.each "/kml//Placemark
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
44
|
+
@xml.elements.each "/kml//Placemark" do |placemark|
|
45
|
+
%w[. MultiGeometry].each do |container|
|
46
|
+
placemark.elements.each "#{container}/Point/coordinates" do |coordinates|
|
47
|
+
coords = coordinates.text.split(',').take(2).map(&:to_f)
|
48
|
+
collection.add_point coords, properties(placemark).merge("styles" => {})
|
49
|
+
end
|
50
|
+
placemark.elements.each "#{container}/LineString/coordinates" do |coordinates|
|
51
|
+
coords = coordinates.text.split(' ').map { |triplet| triplet.split(',')[0..1].map(&:to_f) }
|
52
|
+
collection.add_linestring coords, properties(placemark).merge("styles" => styles(placemark))
|
53
|
+
end
|
54
|
+
placemark.elements.each "#{container}/gx:Track[gx:coord]" do |track|
|
55
|
+
coords = track.collect("gx:coord") { |coord| coord.text.split(?\s).take(2).map(&:to_f) }
|
56
|
+
collection.add_linestring coords, properties(placemark).merge("styles" => styles(placemark))
|
57
|
+
end
|
58
|
+
placemark.elements.each "#{container}/Polygon[outerBoundaryIs/LinearRing/coordinates]" do |polygon|
|
59
|
+
coords = [polygon.elements["outerBoundaryIs/LinearRing/coordinates"].text]
|
60
|
+
coords += polygon.elements.collect("innerBoundaryIs/LinearRing/coordinates", &:text)
|
61
|
+
coords.map! do |text|
|
62
|
+
text.split(' ').map { |triplet| triplet.split(?,).take(2).map(&:to_f) }
|
63
|
+
end
|
64
|
+
collection.add_polygon coords, properties(placemark).merge("styles" => styles(placemark))
|
65
|
+
end
|
59
66
|
end
|
60
|
-
collection.add_polygon coords, properties(placemark).merge("styles" => styles(placemark))
|
61
67
|
end
|
62
68
|
end
|
63
69
|
end
|
data/lib/nswtopo/gis/gps.rb
CHANGED
@@ -1,56 +1,67 @@
|
|
1
1
|
module NSWTopo
|
2
2
|
class Projection
|
3
|
-
def initialize(
|
4
|
-
@
|
5
|
-
raise "no georeferencing found: %s" %
|
3
|
+
def initialize(value)
|
4
|
+
@wkt2 = Projection === value ? value.wkt2 : OS.gdalsrsinfo("-o", "wkt2", "--single-line", value).chomp.strip
|
5
|
+
raise "no georeferencing found: %s" % value if @wkt2.empty?
|
6
6
|
end
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
attr_reader :proj4
|
15
|
-
alias to_s proj4
|
16
|
-
alias to_str proj4
|
8
|
+
attr_reader :wkt2
|
9
|
+
alias to_s wkt2
|
10
|
+
alias to_str wkt2
|
17
11
|
|
18
12
|
def ==(other)
|
19
|
-
|
13
|
+
wkt2 == other.wkt2
|
20
14
|
end
|
21
15
|
|
22
16
|
extend Forwardable
|
23
|
-
delegate :hash => :@
|
17
|
+
delegate :hash => :@wkt2
|
24
18
|
alias eql? ==
|
25
19
|
|
26
|
-
def
|
27
|
-
|
20
|
+
def metres?
|
21
|
+
OS.gdalsrsinfo("-o", "proj4", "--single-line", @wkt2).chomp.split.any?("+units=m")
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.utm(zone, south: true)
|
25
|
+
new("EPSG:32%1d%02d" % [south ? 7 : 6, zone])
|
28
26
|
end
|
29
27
|
|
30
28
|
def self.wgs84
|
31
|
-
new("
|
29
|
+
new("EPSG:4326")
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.from(**params)
|
33
|
+
params.map do |key, value|
|
34
|
+
"+#{key}=#{value}"
|
35
|
+
end.then do |args|
|
36
|
+
new args.join(?\s)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.transverse_mercator(lon_0, lat_0, **params)
|
41
|
+
from proj: "tmerc", datum: "WGS84", lon_0: lon_0, lat_0: lat_0, **params
|
32
42
|
end
|
33
43
|
|
34
|
-
def self.
|
35
|
-
|
44
|
+
def self.oblique_mercator(lonc, lat_0, alpha:, **params)
|
45
|
+
from proj: "omerc", datum: "WGS84", lonc: lonc, lat_0: lat_0, gamma: 0, alpha: alpha, **params
|
36
46
|
end
|
37
47
|
|
38
|
-
def self.azimuthal_equidistant(
|
39
|
-
|
48
|
+
def self.azimuthal_equidistant(lon_0, lat_0)
|
49
|
+
from proj: "aeqd", datum: "WGS84", lon_0: lon_0, lat_0: lat_0
|
40
50
|
end
|
41
51
|
|
42
52
|
def self.utm_zones(collection)
|
43
53
|
collection.reproject_to_wgs84.bounds.first.map do |longitude|
|
44
54
|
(longitude / 6).floor + 31
|
45
|
-
end.
|
55
|
+
end.then do |min, max|
|
46
56
|
min..max
|
47
57
|
end
|
48
58
|
end
|
49
59
|
|
50
|
-
def self.
|
60
|
+
def self.utm_geometry(zone)
|
51
61
|
longitudes = [31, 30].map { |offset| (zone - offset) * 6.0 }
|
52
62
|
latitudes = [-80.0, 84.0]
|
53
|
-
longitudes.product(latitudes).values_at(0,2,3,1)
|
63
|
+
ring = longitudes.product(latitudes).values_at(0,2,3,1,0)
|
64
|
+
GeoJSON.polygon [ring], projection: Projection.wgs84
|
54
65
|
end
|
55
66
|
end
|
56
67
|
end
|
@@ -1,24 +1,97 @@
|
|
1
1
|
module NSWTopo
|
2
2
|
module Shapefile
|
3
|
-
|
3
|
+
class Source
|
4
|
+
def self.===(path)
|
5
|
+
OS.ogrinfo "-ro", "-so", path
|
6
|
+
true
|
7
|
+
rescue OS::Error
|
8
|
+
false
|
9
|
+
end
|
4
10
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
11
|
+
def initialize(path)
|
12
|
+
@path = path
|
13
|
+
end
|
14
|
+
attr_accessor :path
|
15
|
+
|
16
|
+
def layer(**options)
|
17
|
+
Layer.new self, **options
|
18
|
+
end
|
19
|
+
|
20
|
+
def only_layer
|
21
|
+
name, *others = OS.ogrinfo("-ro", "-so", @path).scan(/^\w*\d+: (.*?)(?: \([\w\s]+\))?$/).flatten
|
22
|
+
return nil if others.any?
|
23
|
+
return name if name
|
24
|
+
File.basename(@path, File.extname(@path)).tap do |name|
|
25
|
+
OS.ogrinfo "-ro", "-so", @path, name
|
26
|
+
end
|
27
|
+
rescue OS::Error
|
28
|
+
end
|
29
|
+
|
30
|
+
def layer_info
|
31
|
+
OS.ogrinfo("-ro", "-so", @path).scan(/^\w*\d+: (.*?)(?: \(([\w\s]+)\))?$/).sort_by(&:first).map do |name, geom_type|
|
32
|
+
geom_type ? "#{name} (#{geom_type.delete(?\s)})" : name
|
33
|
+
end
|
34
|
+
end
|
10
35
|
end
|
11
36
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
37
|
+
class Layer
|
38
|
+
NoLayerError = Class.new RuntimeError
|
39
|
+
|
40
|
+
def initialize(source, layer: nil, where: nil, fields: nil, sql: nil, geometry: nil, projection: nil)
|
41
|
+
@source, @layer, @where, @fields, @sql, @geometry, @projection = source, layer, where, fields, sql, geometry, projection
|
42
|
+
end
|
43
|
+
|
44
|
+
def features
|
45
|
+
raise "can't specify both SQL and where clause" if @sql && @where
|
46
|
+
raise "can't specify both SQL and layer name" if @sql && @layer
|
47
|
+
raise "no layer name or SQL specified" unless @layer || @sql
|
48
|
+
sql = ["-sql", sql] if @sql
|
49
|
+
where = ["-where", "(" << Array(@where).join(") AND (") << ")"] if @where
|
50
|
+
srs = ["-t_srs", @projection] if @projection
|
51
|
+
spat = ["-spat", *@geometry.bounds.transpose.flatten, "-spat_srs", @geometry.projection] if @geometry
|
52
|
+
misc = %w[-mapFieldType Date=Integer,DateTime=Integer -dim XY]
|
53
|
+
json = OS.ogr2ogr *(sql || where), *srs, *spat, *misc, *%w[-f GeoJSON -lco RFC7946=NO /vsistdout/], @source.path, *@layer
|
54
|
+
GeoJSON::Collection.load json, **{projection: @projection}.compact
|
55
|
+
rescue OS::Error => error
|
56
|
+
raise unless /Couldn't fetch requested layer (.*)!/ === error.message
|
57
|
+
raise "no such layer: #{$1}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def counts
|
61
|
+
raise NoLayerError, "no layer name provided" unless @layer
|
62
|
+
count = ?_ * @fields.map(&:size).max + "count"
|
63
|
+
where = %Q[WHERE (%s)] % [*@where].join(") AND (") if @where
|
64
|
+
field_list = %Q["%s"] % @fields.join('", "')
|
65
|
+
sql = <<~SQL % [field_list, count, @layer, where, field_list]
|
66
|
+
SELECT %s, count(*) AS "%s"
|
67
|
+
FROM "%s"
|
68
|
+
%s
|
69
|
+
GROUP BY %s
|
70
|
+
SQL
|
71
|
+
json = OS.ogr2ogr *%w[-f GeoJSON -lco RFC7946=NO -dialect sqlite -sql], sql, "/vsistdout/", @source.path
|
72
|
+
JSON.parse(json)["features"].map do |feature|
|
73
|
+
feature["properties"]
|
74
|
+
end.map do |properties|
|
75
|
+
[properties.slice(*@fields), properties[count]]
|
76
|
+
end
|
77
|
+
rescue OS::Error => error
|
78
|
+
raise unless /no such column: (.*)$/ === error.message
|
79
|
+
raise "invalid field: #{$1}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def info
|
83
|
+
raise NoLayerError, "no layer name provided" unless @layer
|
84
|
+
info = OS.ogrinfo *%w[-ro -so -noextent], @source.path, @layer
|
85
|
+
geom_type = info.match(/^Geometry: (.*)$/)&.[](1)&.delete(?\s)
|
86
|
+
count = info.match(/^Feature Count: (\d+)$/)&.[](1)
|
87
|
+
fields = info.scan(/^(.*): (.*?) \(\d+\.\d+\)$/).to_h
|
88
|
+
wkt = info.each_line.slice_after(/^Layer SRS WKT:/).drop(1).first&.slice_before(/^\S/)&.first&.join
|
89
|
+
epsg = OS.gdalsrsinfo("-o", "epsg", wkt)[/\d+/] if wkt and !wkt["unknown"]
|
90
|
+
{ name: @layer, geometry: geom_type, EPSG: epsg, features: count, fields: (fields unless fields.empty?) }.compact
|
91
|
+
rescue OS::Error => error
|
92
|
+
raise unless /Couldn't fetch requested layer (.*)!/ === error.message
|
93
|
+
raise "no such layer: #{$1}"
|
94
|
+
end
|
22
95
|
end
|
23
96
|
end
|
24
97
|
end
|
data/lib/nswtopo/gis.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
require_relative 'gis/projection'
|
2
2
|
require_relative 'gis/geojson'
|
3
3
|
require_relative 'gis/gps'
|
4
|
-
require_relative 'gis/world_file'
|
5
4
|
require_relative 'gis/esri_hdr'
|
6
|
-
require_relative 'gis/
|
5
|
+
require_relative 'gis/arcgis'
|
7
6
|
require_relative 'gis/shapefile'
|
8
7
|
require_relative 'gis/gdal_glob'
|
9
8
|
require_relative 'gis/dem'
|
@@ -14,17 +14,6 @@ module ArrayHelpers
|
|
14
14
|
def in_two
|
15
15
|
each_slice(1 + [length - 1, 0].max / 2)
|
16
16
|
end
|
17
|
-
|
18
|
-
def nearby_pairs(closed = false, &block)
|
19
|
-
Enumerator.new do |yielder|
|
20
|
-
each.with_index do |element1, index|
|
21
|
-
(closed ? rotate(index) : drop(index)).drop(1).each do |element2|
|
22
|
-
break unless block.call [element1, element2]
|
23
|
-
yielder << [element1, element2]
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
17
|
end
|
29
18
|
|
30
19
|
Array.send :include, ArrayHelpers
|
@@ -151,26 +151,46 @@ class Colour
|
|
151
151
|
yellowgreen: [154, 205, 50]
|
152
152
|
YAML
|
153
153
|
|
154
|
-
def initialize(
|
155
|
-
@
|
156
|
-
|
154
|
+
def initialize(value)
|
155
|
+
@string = value
|
156
|
+
@triplet = case value
|
157
|
+
when Colour
|
158
|
+
@string = value.string.dup
|
159
|
+
value.triplet.dup
|
160
|
+
when Array
|
161
|
+
value.take(3).map(&:round)
|
157
162
|
when *COLOURS.keys
|
158
|
-
|
159
|
-
COLOURS[string_or_array]
|
163
|
+
COLOURS[value]
|
160
164
|
when /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i
|
161
165
|
[$1, $2, $3].map { |hex| Integer("0x#{hex}") }
|
162
|
-
when /^rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)$/
|
166
|
+
when /^rgb\((\d{1,3}), *(\d{1,3}), *(\d{1,3})\)$/
|
163
167
|
[$1, $2, $3].map(&:to_i)
|
168
|
+
when /^hsl\((\d{1,3}), *(\d{1,3})%, *(\d{1,3})%\)$/
|
169
|
+
h, s, l = [$1, $2, $3].map(&:to_i)
|
170
|
+
h %= 360;
|
171
|
+
c = (100 - (2 * l - 100).abs) * s / 10000.0
|
172
|
+
x = (60 - (h % 120 - 60).abs) * c / 60.0
|
173
|
+
m = (l - 50 * c) / 100.0
|
174
|
+
r, g, b = case
|
175
|
+
when s == 0 then [0, 0, 0]
|
176
|
+
when h < 60 then [c, x, 0]
|
177
|
+
when h < 120 then [x, c, 0]
|
178
|
+
when h < 180 then [0, c, x]
|
179
|
+
when h < 240 then [0, x, c]
|
180
|
+
when h < 300 then [x, 0, c]
|
181
|
+
when h < 360 then [c, 0, x]
|
182
|
+
end.map do |v|
|
183
|
+
255 * (v + m)
|
184
|
+
end.map(&:to_i)
|
164
185
|
end
|
165
|
-
raise Error, "invalid colour: #{
|
186
|
+
raise Error, "invalid colour: #{value}" unless @triplet&.all?(0..255)
|
187
|
+
@string = "rgb(%i,%i,%i)" % @triplet unless String === @string
|
188
|
+
@string.tr! ?\s, ""
|
166
189
|
end
|
167
|
-
attr_reader :triplet
|
168
190
|
|
169
|
-
|
170
|
-
|
171
|
-
end
|
191
|
+
attr_reader :triplet, :string
|
192
|
+
alias to_s string
|
172
193
|
|
173
|
-
|
174
|
-
|
175
|
-
end
|
194
|
+
extend Forwardable
|
195
|
+
delegate :[] => :@triplet
|
176
196
|
end
|
@@ -1,73 +1,69 @@
|
|
1
1
|
module NSWTopo
|
2
2
|
module ArcGISRaster
|
3
|
-
include Raster, Log
|
3
|
+
include Raster, RasterRender, Log
|
4
4
|
CREATE = %w[url]
|
5
5
|
|
6
6
|
def get_raster(temp_dir)
|
7
|
-
raise "no resolution specified for #{@name}" unless Numeric === @
|
7
|
+
raise "no resolution specified for #{@name}" unless Numeric === @mm_per_px
|
8
8
|
txt_path = temp_dir / "mosaic.txt"
|
9
9
|
vrt_path = temp_dir / "mosaic.vrt"
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
service = ArcGIS::Service.new @url
|
12
|
+
local_bbox = @map.cutline.bbox
|
13
|
+
target_bbox = local_bbox.reproject_to service.projection
|
14
|
+
target_resolution = @mm_per_px * Math::sqrt(target_bbox.first.area / local_bbox.first.area)
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
raise "not a tiled map or image server: #{@url}" unless tile_info = service["tileInfo"]
|
17
|
+
lods = tile_info["lods"]
|
18
|
+
origin = tile_info["origin"].values_at "x", "y"
|
19
|
+
tile_sizes = tile_info.values_at "cols", "rows"
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
tiles = target_bbox.coordinates.first.map do |corner|
|
30
|
-
corner.minus(origin)
|
31
|
-
end.transpose.map(&:minmax).zip(tile_sizes).map do |bound, tile_size|
|
32
|
-
bound / tile_resolution / tile_size
|
33
|
-
end.map do |min, max|
|
34
|
-
(min.floor..max.ceil).each_cons(2).to_a
|
35
|
-
end.inject(&:product).map do |cols, rows|
|
36
|
-
bounds = [cols, rows].zip(tile_sizes).map do |indices, tile_size|
|
37
|
-
indices.times(tile_size * tile_resolution)
|
38
|
-
end.transpose.map do |corner|
|
39
|
-
corner.plus(origin)
|
40
|
-
end.transpose
|
41
|
-
|
42
|
-
bbox = bounds.inject(&:product).values_at(0,2,3,1)
|
43
|
-
next unless target_bbox.first.clip(bbox)
|
21
|
+
lods.sort_by! do |lod|
|
22
|
+
-lod["resolution"]
|
23
|
+
end
|
24
|
+
lod = lods.find do |lod|
|
25
|
+
lod["resolution"] < target_resolution
|
26
|
+
end || lods.last
|
27
|
+
tile_level, tile_resolution = lod.values_at "level", "resolution"
|
44
28
|
|
29
|
+
target_bbox.coordinates.first.map do |corner|
|
30
|
+
corner.minus(origin)
|
31
|
+
end.transpose.map(&:minmax).zip(tile_sizes).map do |bound, tile_size|
|
32
|
+
bound / tile_resolution / tile_size
|
33
|
+
end.map do |min, max|
|
34
|
+
(min.floor..max.ceil).each_cons(2).to_a
|
35
|
+
end.inject(&:product).inject(GeoJSON::Collection.new(projection: service.projection)) do |tiles, (cols, rows)|
|
36
|
+
[cols, rows].zip(tile_sizes).map do |indices, tile_size|
|
37
|
+
indices.times(tile_size * tile_resolution)
|
38
|
+
end.transpose.map do |corner|
|
39
|
+
corner.plus(origin)
|
40
|
+
end.transpose.then do |bounds|
|
41
|
+
ring = bounds.inject(&:product).values_at(0,2,3,1,0)
|
42
|
+
ullr = bounds.inject(&:product).values_at(1,2).flatten
|
45
43
|
row, col = rows[1].abs, cols[0]
|
44
|
+
tiles.add_polygon [ring], ullr: ullr, row: row, col: col
|
45
|
+
end
|
46
|
+
end.clip(target_bbox.first).then do |tiles|
|
47
|
+
tiles.map.with_index do |feature, index|
|
48
|
+
row, col, ullr = feature.values_at("row", "col", "ullr")
|
46
49
|
rel_path = "tile/#{tile_level}/#{row}/#{col}"
|
47
50
|
jpg_path = temp_dir / "#{row}.#{col}" # could be png
|
48
51
|
tif_path = temp_dir / "#{row}.#{col}.tif"
|
49
|
-
|
50
|
-
ullr = bounds.inject(&:product).values_at(1,2).flatten
|
51
|
-
gdal_args = ["-a_srs", projection, "-a_ullr", *ullr, "-of", "GTiff", jpg_path, tif_path]
|
52
|
-
|
53
|
-
[rel_path, jpg_path, gdal_args, tif_path]
|
54
|
-
end.compact
|
55
|
-
tiles.each.with_index do |(rel_path, jpg_path, gdal_args, tif_path), index|
|
52
|
+
gdal_args = ["-a_srs", service.projection, "-a_ullr", *ullr, "-of", "GTiff", jpg_path, tif_path]
|
56
53
|
log_update "%s: retrieving tile %i of %i" % [@name, index + 1, tiles.length]
|
57
|
-
|
54
|
+
service.get(rel_path, blankTile: true) do |response|
|
58
55
|
jpg_path.binwrite response.body
|
59
56
|
end
|
57
|
+
OS.gdal_translate *gdal_args
|
58
|
+
tif_path
|
60
59
|
end
|
61
|
-
end.
|
62
|
-
|
63
|
-
end.map(&:last).tap do |tif_paths|
|
60
|
+
end.tap do |tif_paths|
|
61
|
+
log_update "%s: mosaicing %s tiles" % [@name, tif_paths.length] if tif_paths.length > 1
|
64
62
|
txt_path.write tif_paths.join(?\n)
|
65
|
-
OS.gdalbuildvrt "-input_file_list", txt_path, vrt_path
|
66
63
|
end
|
67
64
|
|
68
|
-
OS.
|
69
|
-
|
70
|
-
return @resolution, vrt_path
|
65
|
+
OS.gdalbuildvrt "-input_file_list", txt_path, vrt_path
|
66
|
+
return vrt_path
|
71
67
|
end
|
72
68
|
end
|
73
69
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module NSWTopo
|
2
2
|
module Contour
|
3
3
|
include Vector, DEM, Log
|
4
|
-
CREATE = %w[interval index smooth simplify thin density min-length no-depression knolls fill]
|
4
|
+
CREATE = %w[interval index auxiliary smooth simplify thin density min-length no-depression knolls fill]
|
5
5
|
DEFAULTS = YAML.load <<~YAML
|
6
6
|
interval: 5
|
7
7
|
smooth: 0.2
|
@@ -9,8 +9,11 @@ module NSWTopo
|
|
9
9
|
min-length: 2.0
|
10
10
|
knolls: 0.2
|
11
11
|
section: 100
|
12
|
-
stroke:
|
12
|
+
stroke: hsl(40,100%,25%)
|
13
13
|
stroke-width: 0.08
|
14
|
+
Auxiliary:
|
15
|
+
stroke-dasharray: 0.5 0.5
|
16
|
+
stroke-dashoffset: 0.5
|
14
17
|
Depression:
|
15
18
|
symbolise:
|
16
19
|
interval: 2.0
|
@@ -26,9 +29,10 @@ module NSWTopo
|
|
26
29
|
max-turn: 20
|
27
30
|
sample: 10
|
28
31
|
minimum-area: 70
|
29
|
-
separation:
|
30
|
-
|
31
|
-
|
32
|
+
separation:
|
33
|
+
self: 40
|
34
|
+
other: 15
|
35
|
+
along: 100
|
32
36
|
YAML
|
33
37
|
|
34
38
|
def margin
|
@@ -36,7 +40,7 @@ module NSWTopo
|
|
36
40
|
end
|
37
41
|
|
38
42
|
def check_geos!
|
39
|
-
json = OS.ogr2ogr "-dialect", "SQLite", "-sql", "SELECT geos_version() AS version", "-f", "GeoJSON", "/vsistdout/", "/vsistdin/" do |stdin|
|
43
|
+
json = OS.ogr2ogr "-dialect", "SQLite", "-sql", "SELECT geos_version() AS version", "-f", "GeoJSON", "-lco", "RFC7946=NO", "/vsistdout/", "/vsistdin/" do |stdin|
|
40
44
|
stdin.write GeoJSON::Collection.new.to_json
|
41
45
|
end
|
42
46
|
raise unless version = JSON.parse(json).dig("features", 0, "properties", "version")
|
@@ -46,7 +50,7 @@ module NSWTopo
|
|
46
50
|
end
|
47
51
|
|
48
52
|
def get_features
|
49
|
-
@simplify ||= [0.5 * @interval / Math::tan(Math::PI * 85 / 180), 0.
|
53
|
+
@simplify ||= [@map.to_mm(0.5 * @interval) / Math::tan(Math::PI * 85 / 180), 0.05].min
|
50
54
|
@index ||= 10 * @interval
|
51
55
|
@params = {
|
52
56
|
"Index" => { "stroke-width" => 2 * @params["stroke-width"] },
|
@@ -71,7 +75,7 @@ module NSWTopo
|
|
71
75
|
|
72
76
|
log_update "%s: generating contour lines" % @name
|
73
77
|
json = OS.gdal_contour "-q", "-a", "elevation", "-i", @interval, "-f", "GeoJSON", "-lco", "RFC7946=NO", blur_path, "/vsistdout/"
|
74
|
-
contours = GeoJSON::Collection.load json, @map.projection
|
78
|
+
contours = GeoJSON::Collection.load json, projection: @map.projection
|
75
79
|
|
76
80
|
if @no_depression.nil?
|
77
81
|
candidates = contours.select do |feature|
|
@@ -94,7 +98,7 @@ module NSWTopo
|
|
94
98
|
|
95
99
|
contours.reject! do |feature|
|
96
100
|
feature.coordinates.last == feature.coordinates.first &&
|
97
|
-
feature.bounds.all? { |min, max| max - min < @knolls
|
101
|
+
feature.bounds.all? { |min, max| max - min < @knolls }
|
98
102
|
end
|
99
103
|
|
100
104
|
contours.each do |feature|
|
@@ -106,14 +110,16 @@ module NSWTopo
|
|
106
110
|
feature["elevation"].zero?
|
107
111
|
end
|
108
112
|
|
109
|
-
|
110
|
-
|
113
|
+
contours.each_slice(100).inject(nil) do |update, features|
|
114
|
+
OS.ogr2ogr "-a_srs", @map.projection, "-nln", "contour", *update, "-simplify", @simplify, *db_flags, db_path, "GeoJSON:/vsistdin/" do |stdin|
|
115
|
+
stdin.write GeoJSON::Collection.new(projection: @map.projection, features: features).to_json
|
116
|
+
end
|
117
|
+
%w[-update -append]
|
111
118
|
end
|
112
119
|
|
113
120
|
if @thin
|
114
121
|
slope_tif_path = temp_dir / "slope.tif"
|
115
122
|
slope_vrt_path = temp_dir / "slope.vrt"
|
116
|
-
min_length = @min_length * @map.scale / 1000.0
|
117
123
|
|
118
124
|
log_update "%s: generating slope masks" % @name
|
119
125
|
OS.gdaldem "slope", blur_path, slope_tif_path, "-compute_edges"
|
@@ -123,24 +129,21 @@ module NSWTopo
|
|
123
129
|
OS.gdal_translate "-srcwin", *srcwin, "-a_nodata", "none", "-of", "VRT", slope_tif_path, slope_vrt_path
|
124
130
|
|
125
131
|
multiplier = @index / @interval
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
else raise "contour thinning not available for specified index interval"
|
135
|
-
end.inject(multiplier) do |count, (*drop)|
|
136
|
-
angle = Math::atan(1000.0 * @index * @density / @map.scale / count) * 180.0 / Math::PI
|
132
|
+
Enumerator.new do |yielder|
|
133
|
+
keep = 0...multiplier
|
134
|
+
until keep.one?
|
135
|
+
keep, drop = keep.count.even? ? keep.each_slice(2).entries.transpose : [[0], keep.drop(1)]
|
136
|
+
yielder << drop
|
137
|
+
end
|
138
|
+
end.inject(multiplier) do |count, drop|
|
139
|
+
angle = Math::atan(@index * @density / count) * 180.0 / Math::PI
|
137
140
|
mask_path = temp_dir / "mask.#{count}.sqlite"
|
138
141
|
|
139
142
|
OS.gdal_contour "-nln", "ring", "-a", "angle", "-fl", angle, *db_flags, slope_vrt_path, mask_path
|
140
143
|
|
141
144
|
OS.ogr2ogr "-update", "-nln", "mask", "-nlt", "MULTIPOLYGON", mask_path, mask_path, "-dialect", "SQLite", "-sql", <<~SQL
|
142
145
|
SELECT
|
143
|
-
ST_Buffer(ST_Buffer(ST_Polygonize(geometry), #{0.5 * min_length}, 6), #{-0.5 * min_length}, 6) AS geometry
|
146
|
+
ST_Buffer(ST_Buffer(ST_Polygonize(geometry), #{0.5 * @min_length}, 6), #{-0.5 * @min_length}, 6) AS geometry
|
144
147
|
FROM ring
|
145
148
|
SQL
|
146
149
|
|
@@ -193,21 +196,25 @@ module NSWTopo
|
|
193
196
|
OS.ogr2ogr "-nln", "thinned", "-update", "-explodecollections", db_path, db_path, "-dialect", "SQLite", "-sql", <<~SQL
|
194
197
|
SELECT ST_LineMerge(ST_Collect(geometry)) AS geometry, id, elevation, modulo, depression, unaltered
|
195
198
|
FROM divided
|
196
|
-
WHERE unmasked OR ST_Length(geometry) < #{min_length}
|
199
|
+
WHERE unmasked OR ST_Length(geometry) < #{@min_length}
|
197
200
|
GROUP BY id, elevation, modulo, unaltered
|
198
201
|
SQL
|
199
202
|
|
200
203
|
OS.ogr2ogr "-nln", "contour", "-update", "-overwrite", db_path, db_path, "-dialect", "SQLite", "-sql", <<~SQL
|
201
204
|
SELECT geometry, id, elevation, modulo, depression
|
202
205
|
FROM thinned
|
203
|
-
WHERE unaltered OR ST_Length(geometry) > #{min_length}
|
206
|
+
WHERE unaltered OR ST_Length(geometry) > #{@min_length}
|
204
207
|
SQL
|
205
208
|
end
|
206
209
|
|
207
210
|
json = OS.ogr2ogr "-f", "GeoJSON", "-lco", "RFC7946=NO", "/vsistdout/", db_path, "contour"
|
208
|
-
GeoJSON::Collection.load(json, @map.projection).each do |feature|
|
211
|
+
GeoJSON::Collection.load(json, projection: @map.projection).each do |feature|
|
209
212
|
elevation, modulo, depression = feature.values_at "elevation", "modulo", "depression"
|
210
|
-
category =
|
213
|
+
category = case
|
214
|
+
when @auxiliary && elevation % (2 * @interval) != 0 then %w[Auxiliary]
|
215
|
+
when modulo.zero? then %w[Index]
|
216
|
+
else %w[Standard]
|
217
|
+
end
|
211
218
|
category << "Depression" if depression == 1
|
212
219
|
feature.clear
|
213
220
|
feature["elevation"] = elevation
|