nswtopo 2.0.0.pre.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/COPYING +674 -0
- data/bin/nswtopo +430 -0
- data/docs/README.md +78 -0
- data/docs/add.md +49 -0
- data/docs/config.md +24 -0
- data/docs/contours.md +37 -0
- data/docs/controls.md +9 -0
- data/docs/declination.md +15 -0
- data/docs/delete.md +15 -0
- data/docs/grid.md +5 -0
- data/docs/info.md +5 -0
- data/docs/init.md +38 -0
- data/docs/layers.md +11 -0
- data/docs/overlay.md +37 -0
- data/docs/relief.md +22 -0
- data/docs/render.md +43 -0
- data/docs/spot-heights.md +23 -0
- data/lib/nswtopo/archive.rb +93 -0
- data/lib/nswtopo/avl_tree.rb +128 -0
- data/lib/nswtopo/config.rb +73 -0
- data/lib/nswtopo/dither.rb +31 -0
- data/lib/nswtopo/font/chrome.rb +59 -0
- data/lib/nswtopo/font/generic.rb +25 -0
- data/lib/nswtopo/font.rb +43 -0
- data/lib/nswtopo/formats/kmz.rb +149 -0
- data/lib/nswtopo/formats/mbtiles.rb +64 -0
- data/lib/nswtopo/formats/pdf.rb +31 -0
- data/lib/nswtopo/formats/svg.rb +69 -0
- data/lib/nswtopo/formats/svgz.rb +13 -0
- data/lib/nswtopo/formats/zip.rb +40 -0
- data/lib/nswtopo/formats.rb +76 -0
- data/lib/nswtopo/geometry/overlap.rb +78 -0
- data/lib/nswtopo/geometry/r_tree.rb +47 -0
- data/lib/nswtopo/geometry/segment.rb +27 -0
- data/lib/nswtopo/geometry/straight_skeleton/collapse.rb +21 -0
- data/lib/nswtopo/geometry/straight_skeleton/interior_node.rb +17 -0
- data/lib/nswtopo/geometry/straight_skeleton/node.rb +50 -0
- data/lib/nswtopo/geometry/straight_skeleton/nodes.rb +295 -0
- data/lib/nswtopo/geometry/straight_skeleton/split.rb +33 -0
- data/lib/nswtopo/geometry/straight_skeleton/vertex.rb +9 -0
- data/lib/nswtopo/geometry/straight_skeleton.rb +6 -0
- data/lib/nswtopo/geometry/vector.rb +91 -0
- data/lib/nswtopo/geometry/vector_sequence.rb +179 -0
- data/lib/nswtopo/geometry.rb +8 -0
- data/lib/nswtopo/gis/arcgis_server/connection.rb +52 -0
- data/lib/nswtopo/gis/arcgis_server.rb +155 -0
- data/lib/nswtopo/gis/dem.rb +70 -0
- data/lib/nswtopo/gis/esri_hdr.rb +77 -0
- data/lib/nswtopo/gis/gdal_glob.rb +41 -0
- data/lib/nswtopo/gis/geojson/collection.rb +94 -0
- data/lib/nswtopo/gis/geojson/line_string.rb +11 -0
- data/lib/nswtopo/gis/geojson/multi_line_string.rb +63 -0
- data/lib/nswtopo/gis/geojson/multi_point.rb +12 -0
- data/lib/nswtopo/gis/geojson/multi_polygon.rb +167 -0
- data/lib/nswtopo/gis/geojson/point.rb +9 -0
- data/lib/nswtopo/gis/geojson/polygon.rb +11 -0
- data/lib/nswtopo/gis/geojson.rb +89 -0
- data/lib/nswtopo/gis/gps/gpx.rb +22 -0
- data/lib/nswtopo/gis/gps/kml.rb +66 -0
- data/lib/nswtopo/gis/gps.rb +20 -0
- data/lib/nswtopo/gis/projection.rb +56 -0
- data/lib/nswtopo/gis/shapefile.rb +24 -0
- data/lib/nswtopo/gis/world_file.rb +19 -0
- data/lib/nswtopo/gis.rb +9 -0
- data/lib/nswtopo/help_formatter.rb +59 -0
- data/lib/nswtopo/helpers/array.rb +30 -0
- data/lib/nswtopo/helpers/colour.rb +176 -0
- data/lib/nswtopo/helpers/concurrently.rb +27 -0
- data/lib/nswtopo/helpers/dir.rb +7 -0
- data/lib/nswtopo/helpers/hash.rb +15 -0
- data/lib/nswtopo/helpers/tar_writer.rb +11 -0
- data/lib/nswtopo/helpers.rb +6 -0
- data/lib/nswtopo/layer/arcgis_raster.rb +73 -0
- data/lib/nswtopo/layer/contour.rb +233 -0
- data/lib/nswtopo/layer/control.rb +94 -0
- data/lib/nswtopo/layer/declination.rb +53 -0
- data/lib/nswtopo/layer/feature.rb +87 -0
- data/lib/nswtopo/layer/grid.rb +120 -0
- data/lib/nswtopo/layer/import.rb +25 -0
- data/lib/nswtopo/layer/labels/fence.rb +20 -0
- data/lib/nswtopo/layer/labels.rb +630 -0
- data/lib/nswtopo/layer/overlay.rb +53 -0
- data/lib/nswtopo/layer/raster.rb +63 -0
- data/lib/nswtopo/layer/relief.rb +143 -0
- data/lib/nswtopo/layer/spot.rb +171 -0
- data/lib/nswtopo/layer/vector.rb +263 -0
- data/lib/nswtopo/layer/vegetation.rb +73 -0
- data/lib/nswtopo/layer.rb +78 -0
- data/lib/nswtopo/log.rb +28 -0
- data/lib/nswtopo/map.rb +296 -0
- data/lib/nswtopo/os.rb +75 -0
- data/lib/nswtopo/safely.rb +13 -0
- data/lib/nswtopo/version.rb +4 -0
- data/lib/nswtopo/zip.rb +15 -0
- data/lib/nswtopo.rb +249 -0
- metadata +142 -0
@@ -0,0 +1,176 @@
|
|
1
|
+
class Colour
|
2
|
+
Error = Class.new RuntimeError
|
3
|
+
|
4
|
+
COLOURS = YAML.load <<~YAML
|
5
|
+
aliceblue: [240, 248, 255]
|
6
|
+
antiquewhite: [250, 235, 215]
|
7
|
+
aqua: [0, 255, 255]
|
8
|
+
aquamarine: [127, 255, 212]
|
9
|
+
azure: [240, 255, 255]
|
10
|
+
beige: [245, 245, 220]
|
11
|
+
bisque: [255, 228, 196]
|
12
|
+
black: [0, 0, 0]
|
13
|
+
blanchedalmond: [255, 235, 205]
|
14
|
+
blue: [0, 0, 255]
|
15
|
+
blueviolet: [138, 43, 226]
|
16
|
+
brown: [165, 42, 42]
|
17
|
+
burlywood: [222, 184, 135]
|
18
|
+
cadetblue: [95, 158, 160]
|
19
|
+
chartreuse: [127, 255, 0]
|
20
|
+
chocolate: [210, 105, 30]
|
21
|
+
coral: [255, 127, 80]
|
22
|
+
cornflowerblue: [100, 149, 237]
|
23
|
+
cornsilk: [255, 248, 220]
|
24
|
+
crimson: [220, 20, 60]
|
25
|
+
cyan: [0, 255, 255]
|
26
|
+
darkblue: [0, 0, 139]
|
27
|
+
darkcyan: [0, 139, 139]
|
28
|
+
darkgoldenrod: [184, 134, 11]
|
29
|
+
darkgray: [169, 169, 169]
|
30
|
+
darkgreen: [0, 100, 0]
|
31
|
+
darkgrey: [169, 169, 169]
|
32
|
+
darkkhaki: [189, 183, 107]
|
33
|
+
darkmagenta: [139, 0, 139]
|
34
|
+
darkolivegreen: [85, 107, 47]
|
35
|
+
darkorange: [255, 140, 0]
|
36
|
+
darkorchid: [153, 50, 204]
|
37
|
+
darkred: [139, 0, 0]
|
38
|
+
darksalmon: [233, 150, 122]
|
39
|
+
darkseagreen: [143, 188, 143]
|
40
|
+
darkslateblue: [72, 61, 139]
|
41
|
+
darkslategray: [47, 79, 79]
|
42
|
+
darkslategrey: [47, 79, 79]
|
43
|
+
darkturquoise: [0, 206, 209]
|
44
|
+
darkviolet: [148, 0, 211]
|
45
|
+
deeppink: [255, 20, 147]
|
46
|
+
deepskyblue: [0, 191, 255]
|
47
|
+
dimgray: [105, 105, 105]
|
48
|
+
dimgrey: [105, 105, 105]
|
49
|
+
dodgerblue: [30, 144, 255]
|
50
|
+
firebrick: [178, 34, 34]
|
51
|
+
floralwhite: [255, 250, 240]
|
52
|
+
forestgreen: [34, 139, 34]
|
53
|
+
fuchsia: [255, 0, 255]
|
54
|
+
gainsboro: [220, 220, 220]
|
55
|
+
ghostwhite: [248, 248, 255]
|
56
|
+
gold: [255, 215, 0]
|
57
|
+
goldenrod: [218, 165, 32]
|
58
|
+
gray: [128, 128, 128]
|
59
|
+
grey: [128, 128, 128]
|
60
|
+
green: [0, 128, 0]
|
61
|
+
greenyellow: [173, 255, 47]
|
62
|
+
honeydew: [240, 255, 240]
|
63
|
+
hotpink: [255, 105, 180]
|
64
|
+
indianred: [205, 92, 92]
|
65
|
+
indigo: [75, 0, 130]
|
66
|
+
ivory: [255, 255, 240]
|
67
|
+
khaki: [240, 230, 140]
|
68
|
+
lavender: [230, 230, 250]
|
69
|
+
lavenderblush: [255, 240, 245]
|
70
|
+
lawngreen: [124, 252, 0]
|
71
|
+
lemonchiffon: [255, 250, 205]
|
72
|
+
lightblue: [173, 216, 230]
|
73
|
+
lightcoral: [240, 128, 128]
|
74
|
+
lightcyan: [224, 255, 255]
|
75
|
+
lightgoldenrodyellow: [250, 250, 210]
|
76
|
+
lightgray: [211, 211, 211]
|
77
|
+
lightgreen: [144, 238, 144]
|
78
|
+
lightgrey: [211, 211, 211]
|
79
|
+
lightpink: [255, 182, 193]
|
80
|
+
lightsalmon: [255, 160, 122]
|
81
|
+
lightseagreen: [32, 178, 170]
|
82
|
+
lightskyblue: [135, 206, 250]
|
83
|
+
lightslategray: [119, 136, 153]
|
84
|
+
lightslategrey: [119, 136, 153]
|
85
|
+
lightsteelblue: [176, 196, 222]
|
86
|
+
lightyellow: [255, 255, 224]
|
87
|
+
lime: [0, 255, 0]
|
88
|
+
limegreen: [50, 205, 50]
|
89
|
+
linen: [250, 240, 230]
|
90
|
+
magenta: [255, 0, 255]
|
91
|
+
maroon: [128, 0, 0]
|
92
|
+
mediumaquamarine: [102, 205, 170]
|
93
|
+
mediumblue: [0, 0, 205]
|
94
|
+
mediumorchid: [186, 85, 211]
|
95
|
+
mediumpurple: [147, 112, 219]
|
96
|
+
mediumseagreen: [60, 179, 113]
|
97
|
+
mediumslateblue: [123, 104, 238]
|
98
|
+
mediumspringgreen: [0, 250, 154]
|
99
|
+
mediumturquoise: [72, 209, 204]
|
100
|
+
mediumvioletred: [199, 21, 133]
|
101
|
+
midnightblue: [25, 25, 112]
|
102
|
+
mintcream: [245, 255, 250]
|
103
|
+
mistyrose: [255, 228, 225]
|
104
|
+
moccasin: [255, 228, 181]
|
105
|
+
navajowhite: [255, 222, 173]
|
106
|
+
navy: [0, 0, 128]
|
107
|
+
oldlace: [253, 245, 230]
|
108
|
+
olive: [128, 128, 0]
|
109
|
+
olivedrab: [107, 142, 35]
|
110
|
+
orange: [255, 165, 0]
|
111
|
+
orangered: [255, 69, 0]
|
112
|
+
orchid: [218, 112, 214]
|
113
|
+
palegoldenrod: [238, 232, 170]
|
114
|
+
palegreen: [152, 251, 152]
|
115
|
+
paleturquoise: [175, 238, 238]
|
116
|
+
palevioletred: [219, 112, 147]
|
117
|
+
papayawhip: [255, 239, 213]
|
118
|
+
peachpuff: [255, 218, 185]
|
119
|
+
peru: [205, 133, 63]
|
120
|
+
pink: [255, 192, 203]
|
121
|
+
plum: [221, 160, 221]
|
122
|
+
powderblue: [176, 224, 230]
|
123
|
+
purple: [128, 0, 128]
|
124
|
+
red: [255, 0, 0]
|
125
|
+
rosybrown: [188, 143, 143]
|
126
|
+
royalblue: [65, 105, 225]
|
127
|
+
saddlebrown: [139, 69, 19]
|
128
|
+
salmon: [250, 128, 114]
|
129
|
+
sandybrown: [244, 164, 96]
|
130
|
+
seagreen: [46, 139, 87]
|
131
|
+
seashell: [255, 245, 238]
|
132
|
+
sienna: [160, 82, 45]
|
133
|
+
silver: [192, 192, 192]
|
134
|
+
skyblue: [135, 206, 235]
|
135
|
+
slateblue: [106, 90, 205]
|
136
|
+
slategray: [112, 128, 144]
|
137
|
+
slategrey: [112, 128, 144]
|
138
|
+
snow: [255, 250, 250]
|
139
|
+
springgreen: [0, 255, 127]
|
140
|
+
steelblue: [70, 130, 180]
|
141
|
+
tan: [210, 180, 140]
|
142
|
+
teal: [0, 128, 128]
|
143
|
+
thistle: [216, 191, 216]
|
144
|
+
tomato: [255, 99, 71]
|
145
|
+
turquoise: [64, 224, 208]
|
146
|
+
violet: [238, 130, 238]
|
147
|
+
wheat: [245, 222, 179]
|
148
|
+
white: [255, 255, 255]
|
149
|
+
whitesmoke: [245, 245, 245]
|
150
|
+
yellow: [255, 255, 0]
|
151
|
+
yellowgreen: [154, 205, 50]
|
152
|
+
YAML
|
153
|
+
|
154
|
+
def initialize(string_or_array)
|
155
|
+
@triplet = case string_or_array
|
156
|
+
when Array then string_or_array.take(3).map(&:round)
|
157
|
+
when *COLOURS.keys
|
158
|
+
@name = string_or_array
|
159
|
+
COLOURS[string_or_array]
|
160
|
+
when /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i
|
161
|
+
[$1, $2, $3].map { |hex| Integer("0x#{hex}") }
|
162
|
+
when /^rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)$/
|
163
|
+
[$1, $2, $3].map(&:to_i)
|
164
|
+
end
|
165
|
+
raise Error, "invalid colour: #{string_or_array}" unless @triplet&.all?(0..255)
|
166
|
+
end
|
167
|
+
attr_reader :triplet
|
168
|
+
|
169
|
+
def mix(other, fraction)
|
170
|
+
Colour.new [triplet, other.triplet].along(fraction.to_f).map(&:to_i)
|
171
|
+
end
|
172
|
+
|
173
|
+
def to_s
|
174
|
+
@name || "#%.2X%.2X%.2X" % triplet
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Concurrently
|
2
|
+
CORES = Etc.nprocessors rescue 1
|
3
|
+
|
4
|
+
def concurrently(threads = CORES, &block)
|
5
|
+
elements = Queue.new
|
6
|
+
threads.times.map do
|
7
|
+
Thread.new do
|
8
|
+
while element = elements.pop
|
9
|
+
block.call element
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end.tap do
|
13
|
+
inject(elements, &:<<).close
|
14
|
+
end.each(&:join)
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def concurrent_groups(threads = CORES, &block)
|
19
|
+
group_by.with_index do |item, index|
|
20
|
+
index % threads
|
21
|
+
end.values.map do |items|
|
22
|
+
Thread.new(items, &block)
|
23
|
+
end.each(&:join)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Enumerator.send :include, Concurrently
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module HashHelpers
|
2
|
+
def deep_merge(other)
|
3
|
+
merge(other) do |key, old_value, new_value|
|
4
|
+
Hash === old_value ? Hash === new_value ? old_value.deep_merge(new_value) : new_value : new_value
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def deep_merge!(other)
|
9
|
+
merge!(other) do |key, old_value, new_value|
|
10
|
+
Hash === old_value ? Hash === new_value ? old_value.deep_merge!(new_value) : new_value : new_value
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
Hash.send :include, HashHelpers
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module NSWTopo
|
2
|
+
module ArcGISRaster
|
3
|
+
include Raster, Log
|
4
|
+
CREATE = %w[url]
|
5
|
+
|
6
|
+
def get_raster(temp_dir)
|
7
|
+
raise "no resolution specified for #{@name}" unless Numeric === @resolution
|
8
|
+
txt_path = temp_dir / "mosaic.txt"
|
9
|
+
vrt_path = temp_dir / "mosaic.vrt"
|
10
|
+
|
11
|
+
ArcGISServer.start @url do |connection, service, projection|
|
12
|
+
local_bbox = @map.bounding_box
|
13
|
+
target_bbox = local_bbox.reproject_to projection
|
14
|
+
target_resolution = @resolution * Math::sqrt(target_bbox.first.area / local_bbox.first.area)
|
15
|
+
|
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
|
+
|
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"
|
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)
|
44
|
+
|
45
|
+
row, col = rows[1].abs, cols[0]
|
46
|
+
rel_path = "tile/#{tile_level}/#{row}/#{col}"
|
47
|
+
jpg_path = temp_dir / "#{row}.#{col}" # could be png
|
48
|
+
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|
|
56
|
+
log_update "%s: retrieving tile %i of %i" % [@name, index + 1, tiles.length]
|
57
|
+
connection.get(rel_path, blankTile: true) do |response|
|
58
|
+
jpg_path.binwrite response.body
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end.each do |rel_path, jpg_path, gdal_args, tif_path|
|
62
|
+
OS.gdal_translate *gdal_args
|
63
|
+
end.map(&:last).tap do |tif_paths|
|
64
|
+
txt_path.write tif_paths.join(?\n)
|
65
|
+
OS.gdalbuildvrt "-input_file_list", txt_path, vrt_path
|
66
|
+
end
|
67
|
+
|
68
|
+
OS.gdal_translate vrt_path, Pathname.pwd / "foo.tif"
|
69
|
+
|
70
|
+
return @resolution, vrt_path
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,233 @@
|
|
1
|
+
module NSWTopo
|
2
|
+
module Contour
|
3
|
+
include Vector, DEM, Log
|
4
|
+
CREATE = %w[interval index smooth simplify thin density min-length no-depression knolls fill]
|
5
|
+
DEFAULTS = YAML.load <<~YAML
|
6
|
+
interval: 5
|
7
|
+
smooth: 0.2
|
8
|
+
density: 4.0
|
9
|
+
min-length: 2.0
|
10
|
+
knolls: 0.2
|
11
|
+
section: 100
|
12
|
+
stroke: "#805100"
|
13
|
+
stroke-width: 0.08
|
14
|
+
Depression:
|
15
|
+
symbolise:
|
16
|
+
interval: 2.0
|
17
|
+
line:
|
18
|
+
stroke-width: 0.12
|
19
|
+
y2: -0.3
|
20
|
+
labels:
|
21
|
+
font-size: 1.4
|
22
|
+
letter-spacing: 0.05
|
23
|
+
orientation: downhill
|
24
|
+
collate: true
|
25
|
+
min-radius: 5
|
26
|
+
max-turn: 20
|
27
|
+
sample: 10
|
28
|
+
minimum-area: 70
|
29
|
+
separation: 40
|
30
|
+
separation-all: 15
|
31
|
+
separation-along: 100
|
32
|
+
YAML
|
33
|
+
|
34
|
+
def margin
|
35
|
+
{ mm: [3 * @smooth, 1].min }
|
36
|
+
end
|
37
|
+
|
38
|
+
def check_geos!
|
39
|
+
json = OS.ogr2ogr "-dialect", "SQLite", "-sql", "SELECT geos_version() AS version", "-f", "GeoJSON", "/vsistdout/", "/vsistdin/" do |stdin|
|
40
|
+
stdin.write GeoJSON::Collection.new.to_json
|
41
|
+
end
|
42
|
+
raise unless version = JSON.parse(json).dig("features", 0, "properties", "version")
|
43
|
+
raise unless (version.split(?-).first.split(?.).map(&:to_i) <=> [3, 3]) >= 0
|
44
|
+
rescue OS::Error, JSON::ParserError, RuntimeError
|
45
|
+
raise "contour thinning requires GDAL with SpatiaLite and GEOS support"
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_features
|
49
|
+
@simplify ||= [0.5 * @interval / Math::tan(Math::PI * 85 / 180), 0.001 * 0.05 * @map.scale].min
|
50
|
+
@index ||= 10 * @interval
|
51
|
+
@params = {
|
52
|
+
"Index" => { "stroke-width" => 2 * @params["stroke-width"] },
|
53
|
+
"labels" => { "fill" => @fill || @params["stroke"] }
|
54
|
+
}.deep_merge(@params)
|
55
|
+
|
56
|
+
check_geos! if @thin
|
57
|
+
raise "%im index interval not a multiple of %im contour interval" % [@index, @interval] unless @index % @interval == 0
|
58
|
+
|
59
|
+
Dir.mktmppath do |temp_dir|
|
60
|
+
dem_path, blur_path = temp_dir / "dem.tif", temp_dir / "dem.blurred.tif"
|
61
|
+
|
62
|
+
if @smooth.zero?
|
63
|
+
get_dem temp_dir, blur_path
|
64
|
+
else
|
65
|
+
get_dem temp_dir, dem_path
|
66
|
+
blur_dem dem_path, blur_path
|
67
|
+
end
|
68
|
+
|
69
|
+
db_flags = @thin ? %w[-f SQLite -dsco SPATIALITE=YES] : ["-f", "ESRI Shapefile"]
|
70
|
+
db_path = temp_dir / "contour"
|
71
|
+
|
72
|
+
log_update "%s: generating contour lines" % @name
|
73
|
+
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
|
75
|
+
|
76
|
+
if @no_depression.nil?
|
77
|
+
candidates = contours.select do |feature|
|
78
|
+
feature.coordinates.last == feature.coordinates.first &&
|
79
|
+
feature.coordinates.anticlockwise?
|
80
|
+
end.each do |feature|
|
81
|
+
feature["depression"] = 1
|
82
|
+
end
|
83
|
+
index = RTree.load(candidates, &:bounds)
|
84
|
+
|
85
|
+
contours.reject! do |feature|
|
86
|
+
next unless feature["depression"] == 1
|
87
|
+
index.search(feature.bounds).none? do |other|
|
88
|
+
next if other == feature
|
89
|
+
feature.coordinates.first.within?(other.coordinates) ||
|
90
|
+
other.coordinates.first.within?(feature.coordinates)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
contours.reject! do |feature|
|
96
|
+
feature.coordinates.last == feature.coordinates.first &&
|
97
|
+
feature.bounds.all? { |min, max| max - min < @knolls * @map.scale / 1000.0 }
|
98
|
+
end
|
99
|
+
|
100
|
+
contours.each do |feature|
|
101
|
+
id, elevation, depression = feature.values_at "ID", "elevation", "depression"
|
102
|
+
feature.properties.replace("id" => id, "elevation" => elevation, "modulo" => elevation % @index, "depression" => depression || 0)
|
103
|
+
end
|
104
|
+
|
105
|
+
contours.reject! do |feature|
|
106
|
+
feature["elevation"].zero?
|
107
|
+
end
|
108
|
+
|
109
|
+
OS.ogr2ogr "-a_srs", @map.projection, "-nln", "contour", "-simplify", @simplify, *db_flags, db_path, "GeoJSON:/vsistdin/" do |stdin|
|
110
|
+
stdin.write contours.to_json
|
111
|
+
end
|
112
|
+
|
113
|
+
if @thin
|
114
|
+
slope_tif_path = temp_dir / "slope.tif"
|
115
|
+
slope_vrt_path = temp_dir / "slope.vrt"
|
116
|
+
min_length = @min_length * @map.scale / 1000.0
|
117
|
+
|
118
|
+
log_update "%s: generating slope masks" % @name
|
119
|
+
OS.gdaldem "slope", blur_path, slope_tif_path, "-compute_edges"
|
120
|
+
json = OS.gdalinfo "-json", slope_tif_path
|
121
|
+
width, height = JSON.parse(json)["size"]
|
122
|
+
srcwin = [ -2, -2, width + 4, height + 4 ]
|
123
|
+
OS.gdal_translate "-srcwin", *srcwin, "-a_nodata", "none", "-of", "VRT", slope_tif_path, slope_vrt_path
|
124
|
+
|
125
|
+
multiplier = @index / @interval
|
126
|
+
case multiplier
|
127
|
+
when 4 then [ [1,3], 2 ]
|
128
|
+
when 5 then [ [1,4], [2,3] ]
|
129
|
+
when 6 then [ [1,4], [2,5], 3 ]
|
130
|
+
when 7 then [ [2,5], [1,3,6], 4 ]
|
131
|
+
when 8 then [ [1,3,5,7], [2,6], 4 ]
|
132
|
+
when 9 then [ [1,4,7], [2,5,8], [3,6] ]
|
133
|
+
when 10 then [ [2,5,8], [1,4,6,9], [3,7] ]
|
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
|
137
|
+
mask_path = temp_dir / "mask.#{count}.sqlite"
|
138
|
+
|
139
|
+
OS.gdal_contour "-nln", "ring", "-a", "angle", "-fl", angle, *db_flags, slope_vrt_path, mask_path
|
140
|
+
|
141
|
+
OS.ogr2ogr "-update", "-nln", "mask", "-nlt", "MULTIPOLYGON", mask_path, mask_path, "-dialect", "SQLite", "-sql", <<~SQL
|
142
|
+
SELECT
|
143
|
+
ST_Buffer(ST_Buffer(ST_Polygonize(geometry), #{0.5 * min_length}, 6), #{-0.5 * min_length}, 6) AS geometry
|
144
|
+
FROM ring
|
145
|
+
SQL
|
146
|
+
|
147
|
+
drop.each do |index|
|
148
|
+
OS.ogr2ogr "-nln", "mask", "-update", "-append", "-explodecollections", "-q", db_path, mask_path, "-dialect", "SQLite", "-sql", <<~SQL
|
149
|
+
SELECT geometry, #{index * @interval} AS modulo
|
150
|
+
FROM mask
|
151
|
+
SQL
|
152
|
+
end
|
153
|
+
|
154
|
+
count - drop.count
|
155
|
+
end
|
156
|
+
|
157
|
+
log_update "%s: thinning contour lines" % @name
|
158
|
+
OS.ogr2ogr "-nln", "divided", "-update", "-explodecollections", db_path, db_path, "-dialect", "SQLite", "-sql", <<~SQL
|
159
|
+
WITH intersecting(contour, mask) AS (
|
160
|
+
SELECT contour.rowid, mask.rowid
|
161
|
+
FROM contour
|
162
|
+
INNER JOIN mask
|
163
|
+
ON
|
164
|
+
mask.modulo = contour.modulo AND
|
165
|
+
contour.rowid IN (
|
166
|
+
SELECT rowid FROM SpatialIndex
|
167
|
+
WHERE
|
168
|
+
f_table_name = 'contour' AND
|
169
|
+
search_frame = mask.geometry
|
170
|
+
) AND
|
171
|
+
ST_Relate(contour.geometry, mask.geometry, 'T********')
|
172
|
+
)
|
173
|
+
|
174
|
+
SELECT contour.geometry, contour.id, contour.elevation, contour.modulo, contour.depression, 1 AS unmasked, 1 AS unaltered
|
175
|
+
FROM contour
|
176
|
+
LEFT JOIN intersecting ON intersecting.contour = contour.rowid
|
177
|
+
WHERE intersecting.contour IS NULL
|
178
|
+
|
179
|
+
UNION SELECT ExtractMultiLinestring(ST_Difference(contour.geometry, ST_Collect(mask.geometry))) AS geometry, contour.id, contour.elevation, contour.modulo, contour.depression, 1 AS unmasked, 0 AS unaltered
|
180
|
+
FROM contour
|
181
|
+
INNER JOIN intersecting ON intersecting.contour = contour.rowid
|
182
|
+
INNER JOIN mask ON intersecting.mask = mask.rowid
|
183
|
+
GROUP BY contour.rowid
|
184
|
+
HAVING min(ST_Relate(contour.geometry, mask.geometry, '**T******'))
|
185
|
+
|
186
|
+
UNION SELECT ExtractMultiLinestring(ST_Intersection(contour.geometry, ST_Collect(mask.geometry))) AS geometry, contour.id, contour.elevation, contour.modulo, contour.depression, 0 AS unmasked, 0 AS unaltered
|
187
|
+
FROM contour
|
188
|
+
INNER JOIN intersecting ON intersecting.contour = contour.rowid
|
189
|
+
INNER JOIN mask ON intersecting.mask = mask.rowid
|
190
|
+
GROUP BY contour.rowid
|
191
|
+
SQL
|
192
|
+
|
193
|
+
OS.ogr2ogr "-nln", "thinned", "-update", "-explodecollections", db_path, db_path, "-dialect", "SQLite", "-sql", <<~SQL
|
194
|
+
SELECT ST_LineMerge(ST_Collect(geometry)) AS geometry, id, elevation, modulo, depression, unaltered
|
195
|
+
FROM divided
|
196
|
+
WHERE unmasked OR ST_Length(geometry) < #{min_length}
|
197
|
+
GROUP BY id, elevation, modulo, unaltered
|
198
|
+
SQL
|
199
|
+
|
200
|
+
OS.ogr2ogr "-nln", "contour", "-update", "-overwrite", db_path, db_path, "-dialect", "SQLite", "-sql", <<~SQL
|
201
|
+
SELECT geometry, id, elevation, modulo, depression
|
202
|
+
FROM thinned
|
203
|
+
WHERE unaltered OR ST_Length(geometry) > #{min_length}
|
204
|
+
SQL
|
205
|
+
end
|
206
|
+
|
207
|
+
json = OS.ogr2ogr "-f", "GeoJSON", "-lco", "RFC7946=NO", "/vsistdout/", db_path, "contour"
|
208
|
+
GeoJSON::Collection.load(json, @map.projection).each do |feature|
|
209
|
+
elevation, modulo, depression = feature.values_at "elevation", "modulo", "depression"
|
210
|
+
category = modulo.zero? ? %w[Index] : %w[Standard]
|
211
|
+
category << "Depression" if depression == 1
|
212
|
+
feature.clear
|
213
|
+
feature["elevation"] = elevation
|
214
|
+
feature["category"] = category
|
215
|
+
feature["label"] = elevation.to_i.to_s if modulo.zero?
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def to_s
|
221
|
+
elevations = features.map do |feature|
|
222
|
+
[feature["elevation"], feature["category"].include?("Index")]
|
223
|
+
end.uniq.sort_by(&:first)
|
224
|
+
range = elevations.map(&:first).minmax
|
225
|
+
interval, index = %i[itself last].map do |selector|
|
226
|
+
elevations.select(&selector).map(&:first).each_cons(2).map { |e0, e1| e1 - e0 }.min
|
227
|
+
end
|
228
|
+
[["%im intervals", interval], ["%im indices", index], ["%im-%im elevation", (range if range.all?)]].select(&:last).map do |label, value|
|
229
|
+
label % value
|
230
|
+
end.join(", ").prepend("%s: " % @name)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module NSWTopo
|
2
|
+
module Control
|
3
|
+
include Vector
|
4
|
+
CREATE = %w[diameter spot font-size colour]
|
5
|
+
DEFAULTS = YAML.load <<~YAML
|
6
|
+
diameter: 7.0
|
7
|
+
colour: darkmagenta
|
8
|
+
stroke-width: 0.25
|
9
|
+
waterdrop:
|
10
|
+
stroke: blue
|
11
|
+
labels:
|
12
|
+
dupe: outline
|
13
|
+
outline:
|
14
|
+
stroke: white
|
15
|
+
fill: none
|
16
|
+
stroke-width: 0.25
|
17
|
+
stroke-opacity: 0.75
|
18
|
+
position: [ aboveright, belowright, aboveleft, belowleft, right, left, above, below ]
|
19
|
+
font-family: sans-serif
|
20
|
+
font-style: normal
|
21
|
+
stroke: none
|
22
|
+
YAML
|
23
|
+
SCALING_PARAMS = <<~YAML
|
24
|
+
fence: 2.0
|
25
|
+
control:
|
26
|
+
symbol:
|
27
|
+
- circle:
|
28
|
+
r: 1.0
|
29
|
+
fill: none
|
30
|
+
hashhouse:
|
31
|
+
symbol:
|
32
|
+
path:
|
33
|
+
d: M 0.0 -1.0 L -0.866 0.5 L 0.866 0.5 Z
|
34
|
+
fill: none
|
35
|
+
anc:
|
36
|
+
symbol:
|
37
|
+
path:
|
38
|
+
d: M 0.7071 0.7071 L -0.7071 0.7071 L -0.7071 -0.7071 L 0.7071 -0.7071 Z
|
39
|
+
fill: none
|
40
|
+
waterdrop:
|
41
|
+
symbol:
|
42
|
+
path:
|
43
|
+
d:
|
44
|
+
M 0 0 m -0.63954,0.063887 -0.0040064,0.32652 0.33453,0.034055 0,-0.38261 -0.33052,0.022034 z
|
45
|
+
M 0 0 m -0.0095612,-0.43108 0,0.11413
|
46
|
+
M 0 0 m 0.11225,-0.43108 0,0.11413
|
47
|
+
M 0 0 m -0.30901,0.04046 c 0.020447,0.0013422 0.029653,0.024004 0.049078,0.031048 0.018777,0.0053462 0.038045,0.0035119 0.05709,0.0020132 0.0063728,-0.0033554 0.042405,-0.030227 0.044414,-0.034299 0.01612,-0.013399 0.037913,-0.04687 0.037717,-0.065861 l 0.0040064,-0.10517 c 0.00020519,-0.0053688 -0.021845,-0.022347 -0.025354,-0.026427 -0.0042688,-0.0049656 0.0055968,-0.05284 -0.0026192,-0.051919 -0.0092762,-0.0035792 -0.013901,0.00044738 -0.02763,-0.0061064 -0.0033824,-0.010245 -0.0050752,-0.053478 0.0081307,-0.051037 0.022266,0.0013424 0.036219,0.0040488 0.061533,-0.0045856 0.015342,0 0.068088,-0.048747 0.083316,-0.048747 l 0.13477,0.00067107 c 0.0086143,0 0.025117,0.022723 0.029266,0.026872 0.019873,0.024221 0.046404,0.029805 0.082646,0.041671 0.0092704,0.0011184 0.041429,-0.0045408 0.044242,0.0020136 0.0063744,0.021228 0.0042104,0.0427 0.0024896,0.050818 -0.0023072,0.010894 -0.023914,-0.00015658 -0.025061,0.013936 -0.0009304,0.011453 -0.00032659,0.031954 -0.0034784,0.046116 -0.019142,0.019707 -0.042333,0.032122 -0.034371,0.040092 l -0.00052567,0.090013 c 0.0024592,0.02049 0.0088222,0.043385 0.016268,0.056554 0.020901,0.046083 0.064072,0.047163 0.06903,0.04844 0.0022744,0.00067108 0.1974,0.025825 0.30749,0.13121 0.0093678,0.00897 0.029965,0.026342 0.053084,0.097154 0.02234,0.068425 0.037538,0.2914 0.030048,0.29447 -0.06374,0.026123 -0.15033,0.053353 -0.2534,0.020043 -0.01286,-0.0041608 0.0013624,-0.088139 -0.0070112,-0.1282 -0.0085866,-0.041085 0.0002729,-0.12091 -0.08864,-0.13672 -0.037049,-0.0065768 -0.15685,0.014316 -0.24088,0.03205 -0.040012,0.0084555 -0.094156,0.013712 -0.12269,0.0080082 -0.017636,-0.0035344 -0.12587,-0.027022 -0.1458,-0.032006 -0.013224,-0.0033104 -0.042701,-0.0050104 -0.087246,0.0088806 -0.033064,0.010312 -0.050813,0.035506 -0.049812,0.026722 0.00073147,-0.00642 0.0012504,-0.371 -8.5944e-05,-0.37768 z
|
48
|
+
M 0 0 m 0.050734,-0.6446 c -0.020293,-0.00022369 -0.052848,0.029116 -0.09202,0.057281 -0.051683,0.032021 -0.11317,0.0094174 -0.16568,-0.00774 -0.049831,-0.019774 -0.12933,-0.0033552 -0.13608,0.058974 0.0013488,0.036319 0.014608,0.092861 0.058748,0.095185 0.074174,-0.010044 0.15057,-0.043112 0.22528,-0.018924 0.029827,0.013175 0.035194,0.033585 0.037678,0.033102 l 0.14636,0 c 0.002484,0.00044738 0.0078512,-0.019931 0.037678,-0.033102 0.074708,-0.024188 0.1511,0.0088806 0.22528,0.018924 0.044141,-0.0023264 0.057457,-0.058867 0.058805,-0.095185 -0.006752,-0.062332 -0.086307,-0.078753 -0.13614,-0.058974 -0.052507,0.017157 -0.114,0.039761 -0.16568,0.00774 -0.039878,-0.028673 -0.072888,-0.058556 -0.093094,-0.057225 -0.00036462,-2.4608e-05 -0.00072.451,-5.1456e-05 -0.0011296,-5.5928e-05 z
|
49
|
+
M 0 0 m -0.16679,-0.26528 c 0.23655,0.036829 0.43804,0.013466 0.43804,0.013466
|
50
|
+
M 0 0 m -0.17245,-0.2153 c 0.23655,0.036826 0.44371,0.017694 0.44371,0.017694
|
51
|
+
fill: none
|
52
|
+
labels:
|
53
|
+
margin: 1.4142
|
54
|
+
font-size: 1.5
|
55
|
+
YAML
|
56
|
+
|
57
|
+
def get_features
|
58
|
+
scaled_params = SCALING_PARAMS.gsub(/\-?\d\.\d+/) { |number| "%.5g" % (number.to_f * 0.5 * @diameter) }
|
59
|
+
scaled_params = YAML.load scaled_params
|
60
|
+
scaled_params["control"]["symbol"] << { "circle" => { "r" => 0.07, "stroke-width" => 0.14, "fill" => "none" } } if @spot
|
61
|
+
@params = scaled_params.deep_merge @params
|
62
|
+
@params["labels"]["font-size"] = @font_size if @font_size
|
63
|
+
@params["labels"]["fill"] = @params["stroke"] = @colour.to_s if @colour
|
64
|
+
points, controls = GPS.load(@path).points, GeoJSON::Collection.new
|
65
|
+
[["control", /^(1?\d\d)W?$/ ],
|
66
|
+
["hashhouse", /^(HH)$/ ],
|
67
|
+
["anc", /^(ANC)$/ ],
|
68
|
+
["waterdrop", /^1?\d\dW$|^W$/],
|
69
|
+
].each do |type, selector|
|
70
|
+
points.each do |point|
|
71
|
+
name = point["name"]
|
72
|
+
next unless name =~ selector
|
73
|
+
properties = [["category", [type, *$1]], ["label", $1]].select(&:last).to_h
|
74
|
+
controls.add_point point.coordinates, properties
|
75
|
+
end
|
76
|
+
end
|
77
|
+
controls
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_s
|
81
|
+
counts = %w[control waterdrop hashhouse].map do |category|
|
82
|
+
waypoints = features.select do |feature|
|
83
|
+
feature["category"].any? category
|
84
|
+
end
|
85
|
+
next if waypoints.empty?
|
86
|
+
count = "%i %s%s" % [waypoints.length, category, waypoints.one? ? nil : ?s]
|
87
|
+
next count unless "control" == category
|
88
|
+
total = features.sum { |feature| feature["label"].to_i.floor(-1) }
|
89
|
+
count << " (%i points)" % total
|
90
|
+
end.compact
|
91
|
+
[@name, counts.join(", ")].join(": ")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|