nswtopo 2.0.0.pre.beta1 → 3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/COPYING +70 -83
  3. data/bin/nswtopo +227 -116
  4. data/docs/README.md +1 -12
  5. data/docs/add.md +1 -1
  6. data/docs/config.md +1 -1
  7. data/docs/contours.md +3 -1
  8. data/docs/init.md +8 -0
  9. data/docs/inspect.md +103 -0
  10. data/docs/move.md +9 -0
  11. data/docs/render.md +16 -7
  12. data/docs/scrape.md +67 -0
  13. data/docs/spot-heights.md +6 -2
  14. data/lib/nswtopo/archive.rb +50 -41
  15. data/lib/nswtopo/chrome.rb +227 -0
  16. data/lib/nswtopo/commands/add.rb +106 -0
  17. data/lib/nswtopo/commands/config.rb +38 -0
  18. data/lib/nswtopo/commands/inspect.rb +74 -0
  19. data/lib/nswtopo/commands/layers.rb +22 -0
  20. data/lib/nswtopo/commands/scrape.rb +79 -0
  21. data/lib/nswtopo/commands.rb +57 -0
  22. data/lib/nswtopo/dither.rb +5 -3
  23. data/lib/nswtopo/font.rb +46 -21
  24. data/lib/nswtopo/formats/gemf.rb +42 -0
  25. data/lib/nswtopo/formats/kmz.rb +26 -24
  26. data/lib/nswtopo/formats/mbtiles.rb +5 -41
  27. data/lib/nswtopo/formats/pdf.rb +82 -17
  28. data/lib/nswtopo/formats/svg.rb +114 -45
  29. data/lib/nswtopo/formats/svgz.rb +2 -2
  30. data/lib/nswtopo/formats/zip.rb +33 -23
  31. data/lib/nswtopo/formats.rb +77 -32
  32. data/lib/nswtopo/geometry/overlap.rb +1 -32
  33. data/lib/nswtopo/geometry/r_tree.rb +16 -10
  34. data/lib/nswtopo/geometry/segment.rb +3 -3
  35. data/lib/nswtopo/geometry/straight_skeleton/nodes.rb +5 -6
  36. data/lib/nswtopo/geometry/vector_sequence.rb +7 -6
  37. data/lib/nswtopo/gis/arcgis/connection.rb +56 -0
  38. data/lib/nswtopo/gis/arcgis/layer/map.rb +163 -0
  39. data/lib/nswtopo/gis/arcgis/layer/query.rb +87 -0
  40. data/lib/nswtopo/gis/arcgis/layer/renderer.rb +66 -0
  41. data/lib/nswtopo/gis/arcgis/layer/statistics.rb +15 -0
  42. data/lib/nswtopo/gis/arcgis/layer.rb +201 -0
  43. data/lib/nswtopo/gis/arcgis/service.rb +57 -0
  44. data/lib/nswtopo/gis/arcgis.rb +3 -0
  45. data/lib/nswtopo/gis/dem.rb +13 -12
  46. data/lib/nswtopo/gis/esri_hdr.rb +8 -2
  47. data/lib/nswtopo/gis/geojson/collection.rb +45 -21
  48. data/lib/nswtopo/gis/geojson/multi_line_string.rb +2 -24
  49. data/lib/nswtopo/gis/geojson/multi_polygon.rb +2 -53
  50. data/lib/nswtopo/gis/geojson/polygon.rb +15 -0
  51. data/lib/nswtopo/gis/geojson.rb +12 -3
  52. data/lib/nswtopo/gis/gps/kml.rb +25 -19
  53. data/lib/nswtopo/gis/gps.rb +2 -0
  54. data/lib/nswtopo/gis/projection.rb +35 -24
  55. data/lib/nswtopo/gis/shapefile.rb +89 -16
  56. data/lib/nswtopo/gis.rb +1 -2
  57. data/lib/nswtopo/helpers/array.rb +0 -11
  58. data/lib/nswtopo/helpers/colour.rb +34 -14
  59. data/lib/nswtopo/layer/arcgis_raster.rb +44 -48
  60. data/lib/nswtopo/layer/colour_mask.rb +5 -0
  61. data/lib/nswtopo/layer/contour.rb +35 -28
  62. data/lib/nswtopo/layer/control.rb +2 -7
  63. data/lib/nswtopo/layer/declination.rb +9 -9
  64. data/lib/nswtopo/layer/feature.rb +36 -22
  65. data/lib/nswtopo/layer/grid.rb +30 -27
  66. data/lib/nswtopo/layer/import.rb +1 -21
  67. data/lib/nswtopo/layer/labels/barrier.rb +39 -0
  68. data/lib/nswtopo/layer/labels.rb +551 -383
  69. data/lib/nswtopo/layer/mask_render.rb +37 -0
  70. data/lib/nswtopo/layer/overlay.rb +2 -2
  71. data/lib/nswtopo/layer/raster.rb +31 -41
  72. data/lib/nswtopo/layer/raster_import.rb +17 -0
  73. data/lib/nswtopo/layer/raster_render.rb +15 -0
  74. data/lib/nswtopo/layer/relief.rb +27 -95
  75. data/lib/nswtopo/layer/spot.rb +63 -62
  76. data/lib/nswtopo/layer/vector/cutout.rb +15 -0
  77. data/lib/nswtopo/layer/vector/knockout.rb +16 -0
  78. data/lib/nswtopo/layer/vector.rb +121 -89
  79. data/lib/nswtopo/layer/vegetation.rb +39 -34
  80. data/lib/nswtopo/layer.rb +30 -16
  81. data/lib/nswtopo/map.rb +202 -109
  82. data/lib/nswtopo/os.rb +5 -27
  83. data/lib/nswtopo/tiled_web_map.rb +54 -0
  84. data/lib/nswtopo/tree_indenter.rb +27 -0
  85. data/lib/nswtopo/version.rb +27 -2
  86. data/lib/nswtopo.rb +6 -199
  87. metadata +41 -22
  88. data/lib/nswtopo/font/chrome.rb +0 -59
  89. data/lib/nswtopo/font/generic.rb +0 -25
  90. data/lib/nswtopo/gis/arcgis_server/connection.rb +0 -52
  91. data/lib/nswtopo/gis/arcgis_server.rb +0 -155
  92. data/lib/nswtopo/gis/geojson/multi_point.rb +0 -12
  93. data/lib/nswtopo/gis/world_file.rb +0 -19
  94. data/lib/nswtopo/layer/labels/fence.rb +0 -20
@@ -1,13 +1,48 @@
1
+ require_relative 'vector/cutout'
2
+ require_relative 'vector/knockout'
3
+
1
4
  module NSWTopo
2
5
  module Vector
3
- SVG_ATTRIBUTES = %w[fill-opacity fill font-family font-size font-style font-variant font-weight letter-spacing opacity stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width stroke text-decoration visibility word-spacing]
4
- FONT_SCALED_ATTRIBUTES = %w[word-spacing letter-spacing stroke-width line-height]
6
+ SVG_ATTRIBUTES = %w[
7
+ fill-opacity
8
+ fill
9
+ font-family
10
+ font-size
11
+ font-style
12
+ font-variant
13
+ font-weight
14
+ letter-spacing
15
+ opacity
16
+ paint-order
17
+ stroke-dasharray
18
+ stroke-dashoffset
19
+ stroke-linecap
20
+ stroke-linejoin
21
+ stroke-miterlimit
22
+ stroke-opacity
23
+ stroke-width
24
+ stroke
25
+ text-decoration
26
+ visibility
27
+ word-spacing
28
+ nswtopo:overprint
29
+ nswtopo:stroke
30
+ nswtopo:fill
31
+ ]
32
+
33
+ FONT_SCALED_ATTRIBUTES = %w[
34
+ word-spacing
35
+ letter-spacing
36
+ stroke-width
37
+ line-height
38
+ ]
39
+
5
40
  SHIELD_X, SHIELD_Y = 1.0, 0.5
6
41
  MARGIN = { mm: 1.0 }
7
42
  VALUE, POINT, ANGLE = "%.5f", "%.5f %.5f", "%.2f"
8
43
 
9
44
  def create
10
- @features = get_features.reproject_to(@map.projection).clip!(@map.bounding_box(MARGIN).coordinates.first)
45
+ @features = get_features.reproject_to(@map.neatline.projection).clip(@map.neatline(**MARGIN))
11
46
  @map.write filename, @features.to_json
12
47
  end
13
48
 
@@ -22,10 +57,6 @@ module NSWTopo
22
57
  extend Forwardable
23
58
  def_delegator :features, :none?, :empty?
24
59
 
25
- def to_mm
26
- @to_mm ||= @map.method(:coords_to_mm)
27
- end
28
-
29
60
  def drawing_features
30
61
  features.explode.reject do |feature|
31
62
  feature["draw"] == false
@@ -55,10 +86,10 @@ module NSWTopo
55
86
  distances = extras.segments.map(&:distance)
56
87
  offsets = midpoints.zip(distances).segments.map(&:transpose).map do |segment, distance|
57
88
  segment.along(distance.first / distance.inject(&:+))
58
- end.zip(points).map(&:difference)
59
- controls = midpoints.segments.zip(offsets).map do |segment, offset|
89
+ end.zip(points).map(&:diff)
90
+ controls = midpoints.segments.zip(offsets).flat_map do |segment, offset|
60
91
  segment.map { |point| [point, point.plus(offset)].along(fraction) }
61
- end.flatten(1).drop(1).each_slice(2).entries.prepend(nil)
92
+ end.drop(1).each_slice(2).entries.prepend(nil)
62
93
  points.zip(controls).map do |point, controls|
63
94
  controls ? "C %s %s %s" % [POINT, POINT, POINT] % [*controls.flatten, *point] : "M %s" % POINT % point
64
95
  end.join(" ")
@@ -74,51 +105,67 @@ module NSWTopo
74
105
  Array(key).any? do |selector|
75
106
  String(selector).split(?\s).to_set <= categories
76
107
  end
77
- end.values.inject(params, &:merge)
108
+ end.values.inject(params, &:deep_merge)
78
109
  end
79
110
 
80
- def render(group, defs)
111
+ def render(knockout:, **, &block)
112
+ defs = REXML::Element.new("defs").tap(&block)
113
+ defs.add_attributes "id" => "#{@name}.defs"
114
+
81
115
  drawing_features.group_by do |feature, categories|
82
116
  categories || Array(feature["category"]).map(&:to_s).map(&method(:categorise)).to_set
83
- end.map do |categories, features|
117
+ end.flat_map do |categories, features|
84
118
  dupes = params_for(categories)["dupe"]
85
119
  Array(dupes).map(&:to_s).map do |dupe|
86
120
  [categories | Set[dupe], [name, *categories, "content"].join(?.)]
87
121
  end.push [categories, features]
88
- end.flatten(1).map do |categories, features|
122
+ end.tap do |ordered|
123
+ params.fetch("order", []).reverse.map(&:split).map(&:to_set).each do |filter|
124
+ ordered.sort_by!.with_index do |(categories, features), index|
125
+ [filter <= categories ? 0 : 1, index]
126
+ end
127
+ end
128
+ end.each do |categories, features|
89
129
  ids = [name, *categories]
130
+ use = REXML::Element.new("use")
131
+ use.add_attributes "id" => ids.join(?.)
132
+
90
133
  case features
91
134
  when String
92
- container = group.add_element "use", "class" => categories.to_a.join(?\s), "xlink:href" => "#%s" % features
135
+ use.add_attributes "href" => "##{features}"
93
136
  when Array
94
- container = group.add_element "g", "class" => categories.to_a.join(?\s)
95
- content = container.add_element "g", "id" => [*ids, "content"].join(?.)
137
+ content = defs.add_element "g", "id" => [*ids, "content"].join(?.)
138
+ use.add_attributes "href" => "#" + [*ids, "content"].join(?.)
96
139
  end
97
- container.add_attribute "id", ids.join(?.) if categories.any?
140
+ use.tap(&block)
98
141
 
99
- commands = params_for categories
100
- font_size, bezier, section = commands.values_at "font-size", "bezier", "section"
101
- commands.slice(*FONT_SCALED_ATTRIBUTES).each do |key, value|
102
- commands[key] = commands[key].to_i * font_size * 0.01 if value =~ /^\d+%$/
103
- end if font_size
142
+ category_params = params_for(categories)
143
+ font_size, stroke_width, bezier, section = category_params.values_at "font-size", "stroke-width", "bezier", "section"
144
+
145
+ category_params.slice(*SVG_ATTRIBUTES).tap do |svg_attributes|
146
+ svg_attributes.slice(*FONT_SCALED_ATTRIBUTES).each do |key, value|
147
+ svg_attributes[key] = svg_attributes[key].to_i * font_size * 0.01 if /^\d+%$/ === value
148
+ end if font_size
149
+ use.add_attributes svg_attributes
150
+ end
104
151
 
105
152
  features.each do |feature, _|
106
153
  case feature
107
154
  when GeoJSON::Point
108
155
  symbol_id = [*ids, "symbol"].join(?.)
109
- transform = "translate(%s) rotate(%s)" % [POINT, ANGLE] % [*feature.coordinates.yield_self(&to_mm), feature.fetch("rotation", @map.rotation) - @map.rotation]
110
- content.add_element "use", "transform" => transform, "xlink:href" => "#%s" % symbol_id
156
+ transform = "translate(%s) rotate(%s)" % [POINT, ANGLE] % [*feature.coordinates, feature.fetch("rotation", @map.rotation) - @map.rotation]
157
+ content.add_element "use", "transform" => transform, "href" => "#%s" % symbol_id
111
158
 
112
159
  when GeoJSON::LineString
113
- linestring = feature.coordinates.map(&to_mm)
160
+ linestring = feature.coordinates
114
161
  (section ? linestring.in_sections(section) : [linestring]).each do |linestring|
115
162
  content.add_element "path", "fill" => "none", "d" => svg_path_data(linestring, bezier: bezier)
116
163
  end
117
164
 
118
165
  when GeoJSON::Polygon
119
166
  path_data = feature.coordinates.map do |ring|
120
- svg_path_data ring.map(&to_mm), bezier: bezier
121
- end.join(" Z ").concat(" Z")
167
+ svg_path_data ring, bezier: bezier
168
+ end.each.with_object("Z").entries.join(?\s)
122
169
  content.add_element "path", "fill-rule" => "nonzero", "d" => path_data
123
170
 
124
171
  when REXML::Element
@@ -132,28 +179,25 @@ module NSWTopo
132
179
  end
133
180
  end if content
134
181
 
135
- commands.each do |command, args|
182
+ category_params.each do |command, args|
136
183
  next unless args
137
184
  args = args.map(&:to_a).inject([], &:+) if Array === args && args.all?(Hash)
138
185
 
139
186
  case command
140
187
  when "blur"
141
188
  filter_id = [*ids, "blur"].join(?.)
142
- container.add_attribute "filter", "url(#%s)" % filter_id
189
+ use.add_attribute "filter", "url(#%s)" % filter_id
143
190
  defs.add_element("filter", "id" => filter_id).add_element "feGaussianBlur", "stdDeviation" => args, "in" => "SourceGraphic"
144
191
 
145
- when "opacity"
146
- if categories.none?
147
- group.add_attribute "style", "opacity:#{args}"
148
- else
149
- container.add_attribute "opacity", args
150
- end
151
-
152
192
  when "symbol"
153
193
  next unless content
154
194
  symbol = defs.add_element "g", "id" => [*ids, "symbol"].join(?.)
155
195
  args.each do |element, attributes|
156
- symbol.add_element element, attributes
196
+ if attributes
197
+ symbol.add_element element, attributes
198
+ else
199
+ symbol.add_element REXML::Document.new(element).root
200
+ end
157
201
  end
158
202
 
159
203
  when "pattern"
@@ -166,25 +210,25 @@ module NSWTopo
166
210
  args.each do |element, attributes|
167
211
  pattern.add_element element, attributes
168
212
  end
169
- container.add_attribute "fill", "url(#%s)" % pattern_id
213
+ use.add_attribute "fill", "url(#%s)" % pattern_id
170
214
 
171
215
  when "symbolise"
172
216
  next unless content
173
- interval, symbols = args.partition do |element, attributes|
174
- element == "interval"
217
+ args, symbols = args.partition do |element, attributes|
218
+ %w[interval offset].include? element
175
219
  end
176
- interval = Hash[interval]["interval"]
220
+ interval, offset = Hash[args].values_at "interval", "offset"
177
221
  symbol_ids = symbols.map.with_index do |(element, attributes), index|
178
222
  symbol_id = [*ids, "symbol", index].join(?.).tap do |symbol_id|
179
223
  defs.add_element("g", "id" => symbol_id).add_element(element, attributes)
180
224
  end
181
225
  end
182
226
  lines_or_rings = features.grep(GeoJSON::LineString).map(&:coordinates)
183
- lines_or_rings += features.grep(GeoJSON::Polygon).map(&:coordinates).flatten(1)
227
+ lines_or_rings += features.grep(GeoJSON::Polygon).flat_map(&:coordinates)
184
228
  lines_or_rings.each do |points|
185
- points.map(&to_mm).sample_at(interval, angle: true).each do |point, angle|
229
+ points.sample_at(interval, angle: true, offset: offset).each do |point, angle|
186
230
  transform = "translate(%s) rotate(%s)" % [POINT, ANGLE] % [*point, 180.0 * angle / Math::PI]
187
- content.add_element "use", "transform" => transform, "xlink:href" => "#%s" % symbol_ids.sample
231
+ content.add_element "use", "transform" => transform, "href" => "#%s" % symbol_ids.sample
188
232
  end
189
233
  end
190
234
 
@@ -195,66 +239,54 @@ module NSWTopo
195
239
  args.each do |element, attributes|
196
240
  symbol.add_element element, attributes
197
241
  end
198
- features.grep(GeoJSON::LineString).map do |feature|
199
- feature.coordinates.map(&to_mm)
200
- end.each do |line|
242
+ features.grep(GeoJSON::LineString).map(&:coordinates).each do |line|
201
243
  case command
202
244
  when "inpoint" then [line.first(2)]
203
245
  when "outpoint" then [line.last(2).rotate]
204
246
  when "endpoint" then [line.first(2), line.last(2).rotate]
205
247
  end.each do |segment|
206
- transform = "translate(%s) rotate(%s)" % [POINT, ANGLE] % [*segment.first, 180.0 * segment.difference.angle / Math::PI]
207
- container.add_element "use", "transform" => transform, "xlink:href" => "#%s" % symbol_id
248
+ transform = "translate(%s) rotate(%s)" % [POINT, ANGLE] % [*segment.first, 180.0 * segment.diff.angle / Math::PI]
249
+ use.add_element "use", "transform" => transform, "href" => "#%s" % symbol_id
208
250
  end
209
251
  end
210
252
 
211
- when "mask"
212
- next unless args && content && content.elements.any?
213
- filter_id, mask_id = %w[raster-mask.filter raster-mask]
214
- mask_contents = defs.elements["mask[@id='%s']/g[@filter]" % mask_id]
215
- mask_contents ||= begin
216
- defs.add_element("filter", "id" => filter_id).add_element "feColorMatrix", "type" => "matrix", "in" => "SourceGraphic", "values" => "0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 -1 1"
217
- defs.add_element("mask", "id" => mask_id).add_element("g", "filter" => "url(#%s)" % filter_id).tap do |mask_contents|
218
- mask_contents.add_element "rect", "width" => "100%", "height" => "100%", "fill" => "none", "stroke" => "none"
219
- end
220
- end
221
- transforms = REXML::XPath.each(content, "ancestor::g[@transform]/@transform").map(&:value)
222
- mask_contents.add_element "use", "xlink:href" => "#%s" % content.attributes["id"], "transform" => (transforms.join(?\s) if transforms.any?)
253
+ when "knockout"
254
+ use.add_attributes "mask" => "url(##{knockout})"
255
+ Knockout.new(use, *args).tap(&block)
256
+
257
+ when "preserve"
258
+ use.add_attributes "mask" => "none"
259
+
260
+ when "cutout", "mask" # mask deprecated
261
+ Cutout.new(use).tap(&block)
223
262
 
224
- when "fence"
263
+ when "barrier", "fence" # fence deprecated
225
264
  next unless content && args
226
- buffer = 0.5 * (Numeric === args ? args : commands.fetch("stroke-width", 0))
227
- features.each do |feature|
228
- next if REXML::Element === feature
229
- yield feature, buffer
265
+ buffer = 0.5 * (Numeric === args ? args : Numeric === stroke_width ? stroke_width : 0)
266
+ features.grep_v(REXML::Element).each do |feature|
267
+ Labels::Barrier.new(feature, buffer).tap(&block)
230
268
  end
231
269
 
232
270
  when "shield"
233
271
  next unless content
234
272
  content.elements.each("text") do |element|
235
- next unless text_length = element.elements["./ancestor-or-self::[@textLength]/@textLength"]&.value&.to_f
236
- shield = REXML::Element.new("g")
237
- width, height = text_length + SHIELD_X * font_size, (1 + SHIELD_Y) * font_size
238
- shield.add_element "rect", "x" => -0.5 * width, "y" => -0.5 * height, "width" => width, "height" => height, "rx" => font_size * 0.3, "ry" => font_size * 0.3, "stroke" => "none", "fill" => args
239
- text_transform = element.attributes.get_attribute "transform"
240
- text_transform.remove
241
- shield.attributes << text_transform
242
- element.parent.elements << shield
243
- shield << element
273
+ case
274
+ when text_length = element.elements["./ancestor-or-self::[@textLength]/@textLength"]&.value&.to_f
275
+ shield = REXML::Element.new("g")
276
+ width, height = text_length + SHIELD_X * font_size, (1 + SHIELD_Y) * font_size
277
+ shield.add_element "rect", "x" => -0.5 * width, "y" => -0.5 * height, "width" => width, "height" => height, "rx" => font_size * 0.3, "ry" => font_size * 0.3, "stroke" => "none", "fill" => args
278
+ text_transform = element.attributes.get_attribute "transform"
279
+ text_transform.remove
280
+ shield.attributes << text_transform
281
+ element.parent.elements << shield
282
+ shield << element
283
+ when href = element.elements["./textPath[@href]/@href"]&.value
284
+ shield = REXML::Element.new("g")
285
+ shield.add_element "use", "href" => href, "stroke-width" => (1 + SHIELD_Y) * font_size, "stroke" => args, "stroke-linecap" => "round"
286
+ element.parent.elements << shield
287
+ shield << element
288
+ end
244
289
  end
245
-
246
- when *SVG_ATTRIBUTES
247
- container.add_attribute command, args
248
- end
249
- end
250
-
251
- next categories, features, container
252
- end.tap do |categorised|
253
- params.fetch("order", []).reverse.map(&:split).map(&:to_set).each do |filter|
254
- categorised.select do |categories, features, container|
255
- filter <= categories
256
- end.reverse.each do |categories, features, container|
257
- group.unshift container.remove
258
290
  end
259
291
  end
260
292
  end
@@ -1,17 +1,17 @@
1
1
  module NSWTopo
2
2
  module Vegetation
3
- include Raster, GDALGlob
4
- CREATE = %w[mapping contrast colour]
3
+ include Raster, MaskRender, GDALGlob
4
+ CREATE = %w[mapping contrast]
5
+ DEFAULTS = YAML.load <<~YAML
6
+ colour: hsl(75,55%,72%)
7
+ YAML
5
8
 
6
9
  def get_raster(temp_dir)
7
- txt_path = temp_dir / "source.txt"
8
- vrt_path = temp_dir / "source.vrt"
9
-
10
+ @params["colour"] = @params["colour"]["woody"] if Hash === @params["colour"]
10
11
  min, max = minmax = @mapping&.values_at("min", "max")
11
- low, high, factor = { "low" => 0, "high" => 100, "factor" => 0.0 }.merge(@contrast || {}).values_at "low", "high", "factor"
12
- woody, nonwoody = { "woody" => "#A6F1A6", "non-woody" => "#FFFFFF" }.merge(@colour || {}).values_at("woody", "non-woody").map { |string| Colour.new string }
12
+ low, high, factor = [0, 100, 0].zip(Array @contrast&.values_at("low", "high", "factor")).map(&:compact).map(&:last)
13
13
 
14
- colour_table = (0..255).map do |index|
14
+ alpha_table = (0..255).map do |index|
15
15
  case
16
16
  when minmax&.all?(Integer) && minmax.all?(0..255)
17
17
  (100.0 * (index - min) / (max - min)).clamp(0.0, 100.0)
@@ -29,45 +29,50 @@ module NSWTopo
29
29
  end.inject(&:-)
30
30
  end.inject(&:/) # sigmoid between 0..1
31
31
  end.map do |x|
32
- nonwoody.mix(woody, x)
32
+ Integer(255 * x)
33
33
  end
34
34
 
35
35
  Dir.chdir(@source ? @source.parent : Pathname.pwd) do
36
36
  gdal_rasters @path
37
- end.tap do |rasters|
38
- raise "no vegetation data file specified" if rasters.none?
37
+ end.each do |path, info|
38
+ raise "can't process vegetation data for #{@name}" unless info["bands"].one?
39
+ raise "can't process vegetation data for #{@name}" unless info.dig("bands", 0, "colorInterpretation") == "Palette"
40
+ raise "can't process vegetation data for #{@name}" unless info.dig("bands", 0, "colorTable", "count") == 256
41
+ end.group_by do |path, info|
42
+ info.dig("bands", 0).values_at("colorTable", "noDataValue")
43
+ end.values.then do |rasters, *others|
44
+ raise "no vegetation data file specified" unless rasters
45
+ raise "can't process vegetation data for #{@name}" if others.any?
46
+ rasters
39
47
  end.group_by do |path, info|
40
48
  Projection.new info.dig("coordinateSystem", "wkt")
41
49
  end.map.with_index do |(projection, rasters), index|
42
- indexed_tif_path = temp_dir / "indexed.#{index}.tif"
43
- indexed_vrt_path = temp_dir / "indexed.#{index}.vrt"
44
- coloured_tif_path = temp_dir / "coloured.#{index}.tif"
45
- tif_path = temp_dir / "output.#{index}.tif"
50
+ vrt_path = temp_dir / "indexed.#{index}.vrt"
51
+ txt_path = temp_dir / "source.txt"
46
52
 
47
53
  txt_path.write rasters.map(&:first).join(?\n)
48
- OS.gdalbuildvrt "-overwrite", "-input_file_list", txt_path, vrt_path
49
- OS.gdal_translate "-projwin", *@map.projwin(projection), "-r", "near", "-co", "TFW=YES", vrt_path, indexed_tif_path
50
- OS.gdal_translate "-of", "VRT", indexed_tif_path, indexed_vrt_path
54
+ OS.gdalbuildvrt "-overwrite", "-r", "nearest", "-input_file_list", txt_path, vrt_path
51
55
 
52
- xml = REXML::Document.new indexed_vrt_path.read
53
- raise "can't process vegetation data for #{@name}" unless xml.elements.each("/VRTDataset/VRTRasterBand/ColorTable", &:itself).one?
54
- raise "can't process vegetation data for #{@name}" unless xml.elements.each("/VRTDataset/VRTRasterBand/ColorTable/Entry", &:itself).count == 256
55
- xml.elements.collect("/VRTDataset/VRTRasterBand/ColorTable/Entry", &:itself).zip(colour_table) do |entry, colour|
56
- entry.attributes["c1"], entry.attributes["c2"], entry.attributes["c3"], entry.attributes["c4"] = *colour.triplet, 255
56
+ xml = REXML::Document.new vrt_path.read
57
+ xml.elements.collect("/VRTDataset/VRTRasterBand/ColorTable/Entry", &:itself).zip(alpha_table) do |entry, alpha|
58
+ entry.attributes["c1"], entry.attributes["c2"], entry.attributes["c3"], entry.attributes["c4"] = alpha, alpha, alpha, 255
57
59
  end
58
- xml.elements.each("/VRTDataset/VRTRasterBand/NoDataValue", &:remove)
59
- indexed_vrt_path.write xml
60
- OS.gdal_translate "-expand", "rgb", indexed_vrt_path, coloured_tif_path
61
60
 
62
- OS.gdalwarp "-s_srs", projection, "-t_srs", @map.projection, "-r", "bilinear", coloured_tif_path, tif_path
63
- next tif_path, Numeric === @resolution ? @resolution : @map.get_raster_resolution(tif_path)
64
- end.transpose.tap do |tif_paths, resolutions|
65
- @resolution = resolutions.min
66
- txt_path.write tif_paths.join(?\n)
67
- OS.gdalbuildvrt "-overwrite", "-input_file_list", txt_path, vrt_path
68
- end
61
+ vrt_path.write xml
62
+ vrt_path
63
+ end.then do |vrt_paths|
64
+ tif_path = temp_dir / "source.tif"
65
+ vrt_path = temp_dir / "source.vrt"
69
66
 
70
- return @resolution, vrt_path
67
+ args = ["-t_srs", @map.projection, "-r", "nearest", "-cutline", "GeoJSON:/vsistdin/", "-crop_to_cutline"]
68
+ args += ["-tr", @mm_per_px, @mm_per_px] if @mm_per_px
69
+ OS.gdalwarp *args, *vrt_paths, tif_path do |stdin|
70
+ stdin.puts @map.cutline.to_json
71
+ end
72
+ OS.gdal_translate "-expand", "gray", "-a_nodata", "none", tif_path, vrt_path
73
+
74
+ return vrt_path
75
+ end
71
76
  end
72
77
  end
73
78
  end
data/lib/nswtopo/layer.rb CHANGED
@@ -1,8 +1,12 @@
1
+ require_relative 'layer/raster_import'
1
2
  require_relative 'layer/raster'
3
+ require_relative 'layer/raster_render'
4
+ require_relative 'layer/mask_render'
2
5
  require_relative 'layer/vector'
3
6
  require_relative 'layer/vegetation'
4
7
  require_relative 'layer/import'
5
8
  require_relative 'layer/arcgis_raster'
9
+ require_relative 'layer/colour_mask'
6
10
  require_relative 'layer/feature'
7
11
  require_relative 'layer/contour'
8
12
  require_relative 'layer/spot'
@@ -15,19 +19,28 @@ require_relative 'layer/labels'
15
19
 
16
20
  module NSWTopo
17
21
  class Layer
18
- TYPES = Set[Vegetation, Import, ArcGISRaster, Feature, Contour, Spot, Overlay, Relief, Grid, Declination, Control, Labels]
22
+ TYPES = Set[Vegetation, Import, ColourMask, ArcGISRaster, Feature, Contour, Spot, Overlay, Relief, Grid, Declination, Control, Labels]
19
23
 
20
24
  def initialize(name, map, params)
25
+ params.delete("min-version").then do |creator_string|
26
+ creator_string ? Version[creator_string] : VERSION
27
+ rescue Version::Error
28
+ raise "layer '%s' has unrecognised version: %s" % [name, creator_string]
29
+ end.then do |min_version|
30
+ raise "layer '%s' requires nswtopo %s, this version: %s" % [name, min_version, VERSION] unless min_version <= VERSION
31
+ end
32
+
21
33
  @type = begin
22
34
  NSWTopo.const_get params["type"]
23
35
  rescue NameError, TypeError
24
36
  end
25
37
 
26
- raise "unrecognised layer type: %s" % params["type"].inspect unless TYPES === @type
38
+ raise "layer '%s' has unrecognised type: %s" % [name, params["type"].inspect] unless TYPES === @type
27
39
  extend @type
28
40
 
29
- @params = @type.const_defined?(:DEFAULTS) ? @type.const_get(:DEFAULTS).transform_keys(&:to_s).merge(params) : params
30
- @name, @map, @source, @path, @resolution = Layer.sanitise(name), map, @params.delete("source"), @params.delete("path"), @params.delete("resolution")
41
+ @params = @type.const_defined?(:DEFAULTS) ? @type.const_get(:DEFAULTS).transform_keys(&:to_s).deep_merge(params) : params
42
+ @name, @map, @source, @path, resolution, ppi = Layer.sanitise(name), map, @params.delete("source"), @params.delete("path"), @params.delete("resolution"), @params.delete("ppi")
43
+ @mm_per_px = ppi ? 25.4 / ppi : resolution ? @map.to_mm(resolution) : nil
31
44
 
32
45
  @type.const_get(:CREATE).map(&:to_s).each do |attr|
33
46
  instance_variable_set ?@ + attr.tr_s(?-, ?_), @params.delete(attr)
@@ -39,23 +52,24 @@ module NSWTopo
39
52
 
40
53
  def level
41
54
  case
42
- when Vegetation == @type then 0
43
- when Import == @type then 1
44
- when ArcGISRaster == @type then 1
45
- when Feature == @type then 2
46
- when Contour == @type then 2
47
- when Spot == @type then 2
48
- when Overlay == @type then 3
49
- when Relief == @type then 4
50
- when Grid == @type then 5
51
- when Declination == @type then 6
52
- when Control == @type then 7
55
+ when Import == @type then 0
56
+ when ArcGISRaster == @type then 0
57
+ when Vegetation == @type then 1
58
+ when ColourMask == @type then 2
59
+ when Feature == @type then 3
60
+ when Contour == @type then 3
61
+ when Spot == @type then 3
62
+ when Overlay == @type then 4
63
+ when Relief == @type then 5
64
+ when Grid == @type then 6
65
+ when Declination == @type then 7
66
+ when Control == @type then 8
53
67
  when Labels == @type then 99
54
68
  end
55
69
  end
56
70
 
57
71
  def <=>(other)
58
- [self, other].map(&:level).inject(&:<=>)
72
+ self.level <=> other.level
59
73
  end
60
74
 
61
75
  def ==(other)