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
data/bin/nswtopo CHANGED
@@ -1,19 +1,19 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # Copyright 2011-2019 Matthew Hollingworth
3
+ # Copyright 2011-2022 Matthew Hollingworth
4
4
  #
5
5
  # This program is free software: you can redistribute it and/or modify
6
- # it under the terms of the GNU General Public License as published by
6
+ # it under the terms of the GNU Affero General Public License as published by
7
7
  # the Free Software Foundation, either version 3 of the License, or
8
8
  # (at your option) any later version.
9
9
  #
10
10
  # This program is distributed in the hope that it will be useful,
11
11
  # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
12
  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
- # GNU General Public License for more details.
13
+ # GNU Affero General Public License for more details.
14
14
  #
15
- # You should have received a copy of the GNU General Public License
16
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  begin
19
19
  require 'open3'
@@ -30,75 +30,80 @@ begin
30
30
 
31
31
  GDAL_VERSION = begin
32
32
  stdout, * = Open3.capture3 "gdalinfo", "--version"
33
- stdout[/\d+(?:\.\d+){1,2}/]
34
- rescue Errno::ENOENT
33
+ /^GDAL (?<version>\d+(?:\.\d+){1,2})/ =~ stdout
34
+ version || raise
35
+ rescue Errno::ENOENT, RuntimeError
35
36
  log_abort "GDAL not installed"
36
37
  end
37
38
 
38
39
  case
39
- when (RUBY_VERSION.split(/\D+/).take(3).map(&:to_i) <=> [2,5]) < 0
40
- log_abort "ruby 2.5 or greater required"
40
+ when (RUBY_VERSION.split(/\D+/).take(3).map(&:to_i) <=> [3,0,4]) < 0
41
+ log_abort "ruby 3.0.4 or greater required"
41
42
  when !Zlib.const_defined?(:GzipFile)
42
43
  log_abort "ruby with GZIP_SUPPORT required"
43
- when (GDAL_VERSION.split(/\D+/).take(3).map(&:to_i) <=> [2,3]) < 0
44
- log_abort "GDAL 2.3 or greater required"
44
+ when (GDAL_VERSION.split(/\D+/).take(3).map(&:to_i) <=> [3,4]) < 0
45
+ log_abort "GDAL 3.4 or greater required"
45
46
  end
46
47
 
47
48
  digits = '\d+(?:_\d+)*'
48
49
  float = "[-+]?(?:#{digits}(?=(.)?)(?:\\.(?:#{digits})?)?|\\.#{digits})(?:[eE][-+]?#{digits})?"
49
- coords = "#{float},#{float}"
50
-
51
- PositiveInt = /\A#{digits}\z/
52
- PositiveFloat = /\A#{float}\z/
53
- NonNegFloat = 0..Float::INFINITY
54
- Dimensions = /\A#{float},#{float}\z/
55
- Margins = /\A#{float}(?:,#{float})?\z/
56
- CoordList = /\A#{coords}(?:,#{coords})*\z/
57
- Rotation = /\A(?:#{float}|magnetic|auto)\z/
58
- AltitudeAngle = 0..90
59
- Opacity = /\A#{float}%?\z/
60
- DashArray = /\A#{float}(?:(?:,#{float})*|(?: #{float})*)\z/
61
-
62
- OptionParser.accept PositiveInt, PositiveInt do |string|
50
+ pair = "#{float},#{float}"
51
+ quad = "#{float},#{float},#{float},#{float}"
52
+ field = '[^\s",]+'
53
+
54
+ PositiveInt = Class.new
55
+ PositiveFloat = Class.new
56
+ NonNegFloat = Class.new
57
+ Dimensions = Class.new
58
+ Inset = Class.new
59
+ Margins = Class.new
60
+ CoordList = Class.new
61
+ Rotation = Class.new
62
+ Opacity = Class.new
63
+ DashArray = Class.new
64
+ Zoom = Class.new
65
+ ZlibLevel = Class.new
66
+ FieldList = Class.new
67
+
68
+ OptionParser.accept PositiveInt, /\A#{digits}\z/ do |string|
63
69
  raise OptionParser::InvalidArgument, string unless string.to_i.positive?
64
70
  string.to_i
65
71
  end
66
72
 
67
- OptionParser.accept PositiveFloat, PositiveFloat do |string|
73
+ OptionParser.accept PositiveFloat, /\A#{float}\z/ do |string|
68
74
  raise OptionParser::InvalidArgument, string unless string.to_f.positive?
69
75
  string.to_f
70
76
  end
71
77
 
72
- OptionParser.accept NonNegFloat, PositiveFloat do |string|
78
+ OptionParser.accept NonNegFloat, /\A#{float}\z/ do |string|
73
79
  raise OptionParser::InvalidArgument, string if string.to_f.negative?
74
80
  string.to_f
75
81
  end
76
82
 
77
- OptionParser.accept Dimensions, Dimensions do |string|
83
+ OptionParser.accept Dimensions, /\A#{pair}\z/ do |string|
78
84
  dimensions = string.split(?,).map(&:to_f)
79
85
  raise OptionParser::InvalidArgument, string unless dimensions.all?(&:positive?)
80
86
  dimensions
81
87
  end
82
88
 
83
- OptionParser.accept Margins, Margins do |string|
89
+ OptionParser.accept Inset, /\A#{quad}(?:,#{quad})*\z/ do |string|
90
+ string.split(?,).map(&:to_f).each_slice(4).entries
91
+ end
92
+
93
+ OptionParser.accept Margins, /\A#{float}(?:,#{float})?\z/ do |string|
84
94
  margins = string.split(?,).map(&:to_f)
85
95
  raise OptionParser::InvalidArgument, string if margins.any?(&:negative?)
86
96
  margins.one? ? margins * 2 : margins
87
97
  end
88
98
 
89
- OptionParser.accept CoordList, CoordList do |string|
99
+ OptionParser.accept CoordList, /\A#{pair}(?:,#{pair})*\z/ do |string|
90
100
  string.split(?,).map(&:to_f).each_slice(2).to_a
91
101
  end
92
102
 
93
- OptionParser.accept Rotation, Rotation do |string|
103
+ OptionParser.accept Rotation, /\A(?:#{float}|magnetic|auto)\z/ do |string|
94
104
  "magnetic" == string ? string : "auto" == string ? string : string.to_f
95
105
  end
96
106
 
97
- OptionParser.accept AltitudeAngle, PositiveFloat do |string|
98
- raise OptionParser::InvalidArgument, string unless AltitudeAngle === string.to_f
99
- string.to_f
100
- end
101
-
102
107
  OptionParser.accept Pathname do |string|
103
108
  path = Pathname(string).expand_path
104
109
  raise OptionParser::InvalidArgument, string unless path.exist?
@@ -106,23 +111,37 @@ begin
106
111
  end
107
112
 
108
113
  OptionParser.accept Colour do |string|
109
- string == "none" ? string : Colour.new(string.downcase).to_s
114
+ string == "none" ? string : Colour.new(string.downcase)
110
115
  rescue Colour::Error
111
116
  raise OptionParser::InvalidArgument, string
112
117
  end
113
118
 
114
- OptionParser.accept Opacity, Opacity do |string|
119
+ OptionParser.accept Opacity, /\A#{float}%?\z/ do |string|
115
120
  opacity = string.end_with?(?%) ? Float(string.chomp ?%) * 0.01 : Float(string)
116
121
  raise OptionParser::InvalidArgument, string unless (0..1) === opacity
117
122
  opacity
118
123
  end
119
124
 
120
- OptionParser.accept DashArray, DashArray do |string|
125
+ OptionParser.accept DashArray, /\A#{float}(?:(?:,#{float})*|(?: #{float})*)\z/ do |string|
121
126
  values = string.split(/[, ]/).map(&:to_f)
122
127
  raise OptionParser::InvalidArgument, string if values.any?(&:negative?)
123
128
  values.join ?\s
124
129
  end
125
130
 
131
+ OptionParser.accept Zoom, /\A(?:#{digits},)?#{digits}\z/ do |string|
132
+ values = string.split(?,).map(&:to_i)
133
+ raise OptionParser::InvalidArgument, string unless values.all?(10..20)
134
+ values
135
+ end
136
+
137
+ OptionParser.accept ZlibLevel, /\A\d\z/ do |string|
138
+ Integer(string)
139
+ end
140
+
141
+ OptionParser.accept FieldList, /\A#{field}(?:,#{field})*\z/ do |string|
142
+ string.split ?,
143
+ end
144
+
126
145
  ansi = lambda do |string|
127
146
  string.to_s.gsub(/\*([-a-zA-Z0-9]+)\*/) do
128
147
  "\e[1m%s\e[0m" % $1
@@ -130,7 +149,7 @@ begin
130
149
  "\e[4m%s\e[0m" % $1
131
150
  end.gsub(/~([-a-zA-Z0-9]+)~/) do
132
151
  "\e[3m%s\e[0m" % $1
133
- end
152
+ end.prepend("\r\e[K")
134
153
  end
135
154
 
136
155
  plain = lambda do |string|
@@ -154,6 +173,7 @@ begin
154
173
  controls add rogaine control markers
155
174
  overlay add KML or GPX overlay
156
175
  delete delete map layer
176
+ move move map layer
157
177
  render render map in various formats
158
178
  layers list available map layers
159
179
  config configure nswtopo
@@ -188,15 +208,16 @@ begin
188
208
  *nswtopo* *init* - initialise map bounds and scale
189
209
  usage: _nswtopo_ _init_ [~options~] <map.tgz>
190
210
  EOF
191
- parser.on "-s", "--scale <scale>", PositiveInt, "scale of map (default 25000)"
192
- parser.on "-b", "--bounds <bounds.kml>", Pathname, "bounds for map as KML or GPX file"
193
- parser.on "-c", "--coords <x1,y1,...>", CoordList, "bounds for map as one or more WGS84",
194
- "longitude/latitude pairs"
195
- parser.on "-d", "--dimensions <width,height>", Dimensions, "dimensions of map in mm"
196
- parser.on "-m", "--margins <x[,y]>", Margins, "map margins in mm"
197
- parser.on "-r", "--rotation <rotation>", Rotation, "map rotation angle in clockwise",
198
- "degrees, 'auto' or 'magnetic'"
199
- parser.on "-o", "--overwrite", "overwrite existing map file"
211
+ parser.on "-s", "--scale <scale>", PositiveInt, "scale of map (default 25000)"
212
+ parser.on "-b", "--bounds <bounds.kml>", Pathname, "bounds for map as KML or GPX file"
213
+ parser.on "-c", "--coords <x1,y1,...>", CoordList, "bounds for map as one or more WGS84",
214
+ "longitude/latitude pairs"
215
+ parser.on "-d", "--dimensions <width,height>", Dimensions, "map dimensions in mm"
216
+ parser.on "-r", "--rotation <rotation>", Rotation, "map rotation angle in clockwise",
217
+ "degrees, 'auto' or 'magnetic'"
218
+ parser.on "-m", "--margins <x[,y]>", Margins, "map margins in mm"
219
+ parser.on "-i", "--inset <x1,y1,x2,y2>", Inset, "map inset coordinates in mm"
220
+ parser.on "-o", "--overwrite", "overwrite existing map file"
200
221
 
201
222
  when "info"
202
223
  parser.banner = <<~EOF
@@ -204,72 +225,84 @@ begin
204
225
  usage: _nswtopo_ _info_ [~options~] <map.tgz>
205
226
  EOF
206
227
  parser.on "-e", "--empty", "show empty layers"
228
+ parser.on "-j", "--json", "show map bounds as GeoJSON string"
229
+ parser.on "-p", "--proj", "show map projection as a proj string"
230
+ parser.on "-w", "--wkt", "show map projection as a WKT2 string"
207
231
 
208
232
  when "add"
209
233
  parser.banner = <<~EOF
210
234
  *nswtopo* *add* - add named map layer
211
235
  usage: _nswtopo_ _add_ [~options~] <map.tgz> <layer> [<layer> ...]
212
236
  EOF
213
- parser.on "-r", "--resolution <resolution>", PositiveFloat, "raster layer resolution in metres"
237
+ parser.on "-r", "--resolution <resolution>", PositiveFloat, "raster resolution in metres per pixel"
238
+ parser.on "--ppi <ppi>", PositiveFloat, "raster resolution in pixels per inch"
239
+ parser.on "-o", "--opacity <opacity>", Opacity, "layer opacity (between 0 and 1)"
214
240
  parser.on "-p", "--path <path>", Pathname, "source data path for layer"
215
241
  parser.on "-a", "--after <layer>", "insert after specified layer"
216
242
  parser.on "-b", "--before <layer>", "insert before specified layer"
217
243
  parser.on "-c", "--replace <layer>", "replace specified layer"
218
244
  parser.on "-o", "--overwrite", "overwrite layer if it already exists"
245
+ parser.on "-s", "--strict", "don't continue if a layer fails"
219
246
 
220
247
  when "contours"
221
248
  parser.banner = <<~EOF
222
249
  *nswtopo* *contours* - add contours from elevation data
223
250
  usage: _nswtopo_ _contours_ [~options~] <map.tgz> <dem.zip>
224
251
  EOF
225
- parser.on "-i", "--interval <interval>", PositiveInt, "contour interval in metres (default %s)" % NSWTopo::Contour::DEFAULTS["interval"]
226
- parser.on "-x", "--index <index>", PositiveInt, "index interval in metres"
227
- parser.on "-s", "--smooth <radius>", NonNegFloat, "DEM smoothing radius in mm (default %s)" % NSWTopo::Contour::DEFAULTS["smooth"]
228
- parser.on "-t", "--thin", "thin intermediate contours in steep areas"
229
- # parser.on "-d", "--density <density>", PositiveFloat, "maximum lines/mm before thinning occurs"
230
- # parser.on "-m", "--min-length <length>", PositiveFloat, "minimum length before contour thinning in mm"
231
- parser.on "--no-depression", "don't show or clean depression contours"
232
- parser.on "-k", "--knolls <size>", NonNegFloat, "minimum knoll size in mm (default %s)" % NSWTopo::Contour::DEFAULTS["knolls"]
233
- parser.on "-a", "--after <layer>", "insert after specified layer"
234
- parser.on "-b", "--before <layer>", "insert before specified layer"
235
- parser.on "-c", "--replace <layer>", "replace specified layer"
236
- parser.on "--stroke <colour>", Colour, "stroke colour (name or RGB triplet)"
237
- parser.on "--stroke-width <width>", PositiveFloat, "stroke width in mm"
238
- parser.on "--fill <colour>", Colour, "label colour (defaults to stroke colour)"
252
+ parser.on "-i", "--interval <interval>", PositiveInt, "contour interval in metres (default %s)" % NSWTopo::Contour::DEFAULTS["interval"]
253
+ parser.on "-x", "--index <index>", PositiveInt, "index interval in metres"
254
+ parser.on "-a", "--auxiliary", "show auxiliary contours"
255
+ parser.on "-s", "--smooth <radius>", NonNegFloat, "DEM smoothing radius in mm (default %s)" % NSWTopo::Contour::DEFAULTS["smooth"]
256
+ parser.on "-t", "--thin", "thin intermediate contours in steep areas"
257
+ parser.on "-d", "--density <density>", PositiveFloat, "thinning threshold (default %s lines/mm)" % NSWTopo::Contour::DEFAULTS["density"]
258
+ # parser.on "-m", "--min-length <length>", PositiveFloat, "minimum length before contour thinning in mm"
259
+ parser.on "--no-depression", "don't show or clean depression contours"
260
+ parser.on "-k", "--knolls <size>", NonNegFloat, "minimum knoll size in mm (default %s)" % NSWTopo::Contour::DEFAULTS["knolls"]
261
+ parser.on "-a", "--after <layer>", "insert after specified layer"
262
+ parser.on "-b", "--before <layer>", "insert before specified layer"
263
+ parser.on "-c", "--replace <layer>", "replace specified layer"
264
+ parser.on "--stroke <colour>", Colour, "stroke colour (name or RGB triplet)"
265
+ parser.on "--stroke-width <width>", PositiveFloat, "stroke width in mm (default %s)" % NSWTopo::Contour::DEFAULTS["stroke-width"]
266
+ parser.on "--fill <colour>", Colour, "label colour (defaults to stroke colour)"
267
+ parser.on "-r", "--resolution <resolution>", PositiveFloat, "DEM processing resolution in metres"
239
268
 
240
269
  when "spot-heights"
241
270
  parser.banner = <<~EOF
242
271
  *nswtopo* *spot-heights* - add spot heights from elevation data
243
272
  usage: _nswtopo_ _spot-heights_ [~options~] <map.tgz> <dem.zip>
244
273
  EOF
245
- parser.on "-s", "--smooth <radius>", NonNegFloat, "DEM smoothing radius in mm (default %s)" % NSWTopo::Spot::DEFAULTS["smooth"]
246
- parser.on "--spacing <spacing>", PositiveFloat, "minimum spot spacing in mm (default %i)" % NSWTopo::Spot::DEFAULTS["spacing"]
247
- parser.on "-p", "--prefer <knolls|saddles>", %w[knolls saddles], "preferred spot locations"
248
- parser.on "-a", "--after <layer>", "insert after specified layer"
249
- parser.on "-b", "--before <layer>", "insert before specified layer"
250
- parser.on "-c", "--replace <layer>", "replace specified layer"
274
+ parser.on "-s", "--smooth <radius>", NonNegFloat, "DEM smoothing radius in mm (default %s)" % NSWTopo::Spot::DEFAULTS["smooth"]
275
+ parser.on "--spacing <spacing>", PositiveFloat, "minimum spot spacing in mm (default %i)" % NSWTopo::Spot::DEFAULTS["spacing"]
276
+ parser.on "-p", "--prefer <knolls|saddles>", %w[knolls saddles], "preferred spot locations"
277
+ parser.on "-e", "--extent <extent>", PositiveFloat, "minimum feature extent in mm (default %i)" % NSWTopo::Spot::DEFAULTS["extent"]
278
+ parser.on "-a", "--after <layer>", "insert after specified layer"
279
+ parser.on "-b", "--before <layer>", "insert before specified layer"
280
+ parser.on "-c", "--replace <layer>", "replace specified layer"
281
+ parser.on "-r", "--resolution <resolution>", PositiveFloat, "DEM processing resolution in metres"
251
282
 
252
283
  when "relief"
253
284
  parser.banner = <<~EOF
254
285
  *nswtopo* *relief* - add shaded relief
255
286
  usage: _nswtopo_ _relief_ [~options~] <map.tgz> <dem.zip>
256
287
  EOF
257
- parser.on "-r", "--resolution <resolution>", PositiveFloat, "resolution in metres (default %i)" % NSWTopo::Relief::DEFAULTS["resolution"]
258
- parser.on "-o", "--opacity <opacity>", Opacity, "opacity (default %s)" % NSWTopo::Relief::DEFAULTS["opacity"]
259
- parser.on "-a", "--altitude <altitude>", AltitudeAngle, "altitude angle in degrees (default %i)" % NSWTopo::Relief::DEFAULTS["altitude"]
260
- parser.on "-z", "--azimuth <azimuth>", Float, "azimuth in degrees (default %i)" % NSWTopo::Relief::DEFAULTS["azimuth"]
261
- parser.on "-s", "--sources <sources>", PositiveInt, "number of light sources (default %i)" % NSWTopo::Relief::DEFAULTS["sources"]
262
- parser.on "-y", "--yellow <fraction>", Opacity, "yellow illumination as a fraction",
263
- "of shading (default %s)" % NSWTopo::Relief::DEFAULTS["yellow"]
264
- parser.on "-f", "--factor <factor>", PositiveFloat, "exaggeration factor (default %s)" % NSWTopo::Relief::DEFAULTS["factor"]
288
+ parser.on "-r", "--resolution <resolution>", PositiveFloat, "resolution in metres per pixel"
289
+ parser.on "-p", "--ppi <ppi>", PositiveFloat, "resolution in pixels per inch"
290
+ parser.on "-o", "--opacity <opacity>", Opacity, "opacity (default %s)" % NSWTopo::Relief::DEFAULTS["opacity"]
291
+ parser.on "--shade <colour>", Colour, "shade colour (default %s)" % NSWTopo::Relief::DEFAULTS["shade"]
292
+ parser.on "-m", "--method <igor|combined>", %w[igor combined], "relief shading method (default %s)" % NSWTopo::Relief::DEFAULTS["method"]
293
+ parser.on "-z", "--azimuth <azimuth>", Float, "azimuth in degrees (default %i)" % NSWTopo::Relief::DEFAULTS["azimuth"]
294
+ parser.on "-f", "--factor <factor>", PositiveFloat, "exaggeration factor (default %s)" % NSWTopo::Relief::DEFAULTS["factor"]
265
295
 
266
296
  when "grid"
267
297
  parser.banner = <<~EOF
268
298
  *nswtopo* *grid* - add UTM grid
269
299
  usage: _nswtopo_ _grid_ [~options~] <map.tgz>
270
300
  EOF
271
- parser.on "-i", "--interval <interval>", PositiveFloat, "interval between grid lines in",
272
- "metres (default %i)" % NSWTopo::Grid::DEFAULTS["interval"]
301
+ parser.on "-i", "--interval <interval>", PositiveFloat, "interval between grid lines in",
302
+ "metres (default %i)" % NSWTopo::Grid::DEFAULTS["interval"]
303
+ parser.on "-u", "--unlabeled", "don't add grid labels"
304
+ parser.on "-b", "--border", "add map border"
305
+ parser.on "--stroke-width <width>", PositiveFloat, "stroke width in mm (default %s)" % NSWTopo::Grid::DEFAULTS["stroke-width"]
273
306
 
274
307
  when "declination"
275
308
  parser.banner = <<~EOF
@@ -292,6 +325,7 @@ begin
292
325
  parser.on "-s", "--spot", "add spots at centres"
293
326
  parser.on "-c", "--colour <colour>", Colour, "colour of markers and labels",
294
327
  "(name or RGB triplet)"
328
+ parser.on "-k", "--knockout <width>", NonNegFloat, "symbol knockout width in mm (default %s)" % NSWTopo::Control::DEFAULTS["knockout"]
295
329
  parser.on "-f", "--font-size <font-size>", PositiveFloat, "font size for labels in mm"
296
330
 
297
331
  when "overlay"
@@ -321,22 +355,30 @@ begin
321
355
  usage: _nswtopo_ _delete_ [~options~] <map.tgz> <layer> [<layer> ...]
322
356
  EOF
323
357
 
358
+ when "move"
359
+ parser.banner = <<~EOF
360
+ *nswtopo* *move* - move map layer
361
+ usage: _nswtopo_ _move_ [~options~] <map.tgz> <layer>
362
+ EOF
363
+ parser.on "-a", "--after <layer>", "insert after specified layer"
364
+ parser.on "-b", "--before <layer>", "insert before specified layer"
365
+
324
366
  when "render"
325
367
  parser.banner = <<~EOF
326
368
  *nswtopo* *render* - render map in various formats
327
- usage: _nswtopo_ _render_ [~options~] <map.tgz> [<format-or-path> ...]
369
+ usage: _nswtopo_ _render_ [~options~] <map.tgz|map.svg> [<format-or-path> ...]
328
370
  formats: #{NSWTopo::Formats.extensions.sort.join ?\s}
329
371
  default: svg
330
372
  EOF
331
- parser.on "-p", "--ppi <ppi>", PositiveInt, "resolution for raster formats in pixels",
332
- "per inch (default %i)" % NSWTopo::Formats::PPI
333
- parser.on "-z", "--zoom <zoom>", Integer, "maximum mbtiles zoom level (default %i)" % NSWTopo::Formats::Mbtiles::ZOOM
334
- parser.on "-d", "--dither", "use indexed colour for raster formats"
335
- parser.on "-w", "--worldfile", "save additional projection (.prj) and",
336
- "world file (.wld) for raster formats"
337
- parser.on "-o", "--overwrite", "overwrite existing output files"
338
- parser.on "-e", "--external <map.svg>", Pathname, "render from externally edited SVG"
339
- parser.on "-f", "--force", "force regeneration of cached SVG"
373
+ parser.on "-p", "--ppi <ppi>", PositiveInt, "resolution for raster formats in pixels",
374
+ "per inch (default %i)" % NSWTopo::Formats::PPI
375
+ parser.on "-z", "--zoom <zoom>", Zoom, "maximum tile zoom (10-20, default %i)" % NSWTopo::TiledWebMap::DEFAULT_ZOOM
376
+ parser.on "-b", "--background <colour>", Colour, "background colour (name or RGB triplet)"
377
+ parser.on "-d", "--dither", "use indexed colour for raster formats"
378
+ parser.on "-w", "--worldfile", "save additional projection (.prj) and",
379
+ "world file (.wld) for raster formats"
380
+ parser.on "-o", "--overwrite", "overwrite existing output files"
381
+ parser.on "-f", "--force", "force regeneration of cached SVG"
340
382
 
341
383
  when "layers"
342
384
  parser.banner = <<~EOF
@@ -351,11 +393,46 @@ begin
351
393
  EOF
352
394
  parser.on "-d", "--delete <name>", "delete configuration setting"
353
395
  parser.on "-c", "--chrome <path>", Pathname, "set path for Google Chrome"
354
- parser.on "-f", "--firefox <path>", Pathname, "set path for Firefox"
355
396
  parser.on "-p", "--path <path>", Pathname, "set path for given layer"
356
397
  parser.on "-r", "--resolution <resolution>", PositiveFloat, "set resolution for given layer"
357
398
  parser.on "--layer-dir <path>", Pathname, "set an extra layer directory"
358
399
  parser.on "--[no-]labelling", "enable or disable map labelling"
400
+ parser.on "--[no-]debug", "enable or disable label debuging"
401
+ parser.on "--[no-]gpu", "enable or disable Chrome GPU usage"
402
+ parser.on "--[no-]versioning", "enable or disable map version checking"
403
+ parser.on "-z", "--zlib-level <0-9>", ZlibLevel, "set zlib compression level"
404
+ parser.on "-k", "--knockout <width>", NonNegFloat, "set label knockout width in mm"
405
+
406
+ when "scrape"
407
+ parser.banner = <<~EOF
408
+ *nswtopo* *scrape* - scrape data from an ArcGIS REST endpoint
409
+ usage: _nswtopo_ _scrape_ [~options~] <url> <path>
410
+ EOF
411
+ parser.on "-l", "--layer <layer>", String, "name of ArcGIS service layer"
412
+ parser.on "-i", "--id <id>", Integer, "id number of layer"
413
+ parser.on "-w", "--where <where>", String, "filtering clause"
414
+ parser.on "-c", "--coords <x0,y0,x1,y1>", CoordList, "WGS84 coordinates of bounding box corners"
415
+ parser.on "-n", "--name <name>", /^\w+$/, "name of saved layer"
416
+ parser.on "-f", "--fields <field,...>", FieldList, "comma-separated list of fields"
417
+ parser.on "-d", "--decode", "convert coded values where possible"
418
+ parser.on "-e", "--epsg <number>", PositiveInt, "EPSG number for reprojection"
419
+ parser.on "-p", "--paginate <number>", PositiveInt, "number of records per request"
420
+ parser.on "--concat", "collect all features before saving"
421
+ parser.on "-u", "--unique <field>", String, "field for counting map-only features"
422
+
423
+ when "inspect"
424
+ parser.banner = <<~EOF
425
+ *nswtopo* *inspect* - inspect data from an ArcGIS REST endpoint or local data source
426
+ usage: _nswtopo_ inspect [~options~] <url-or-path>
427
+ EOF
428
+ parser.on "-l", "--layer <layer>", String, "name of layer"
429
+ parser.on "-i", "--id <id>", Integer, "id number of layer"
430
+ parser.on "-w", "--where <where>", String, "filtering clause"
431
+ parser.on "-c", "--coords <x0,y0,x1,y1>", CoordList, "WGS84 coordinates of bounding box corners"
432
+ parser.on "-f", "--fields <field,...>", FieldList, "comma-separated list of fields"
433
+ parser.on "-d", "--decode", "convert coded values where possible"
434
+ parser.on "--codes", "show coded values for an ArcGIS Layer"
435
+ parser.on "--countwise", "sort fields by count instead of value"
359
436
 
360
437
  when nil
361
438
  raise OptionParser::MissingArgument, "no command specified"
@@ -381,50 +458,84 @@ begin
381
458
  case command
382
459
  when "layers"
383
460
  raise OptionParser::NeedlessArgument, ARGV if ARGV.any?
384
- NSWTopo.layers options
461
+ NSWTopo.layers **options
385
462
  exit
386
463
  when "config"
387
464
  layer = ARGV.shift
388
465
  raise OptionParser::NeedlessArgument, ARGV if ARGV.any?
389
466
  NSWTopo.config *layer, **options
390
467
  exit
468
+ when "scrape"
469
+ url, path = ARGV.shift, ARGV.shift
470
+ raise OptionParser::MissingArgument, "no URL specified" unless url
471
+ raise OptionParser::MissingArgument, "no path specified" unless path
472
+ raise OptionParser::NeedlessArgument, ARGV if ARGV.any?
473
+ path = Pathname(path).expand_path
474
+ raise OptionParser::InvalidArgument, "invalid path: #{path}" unless path.parent.directory?
475
+ raise OptionParser::InvalidOption, "can't specify both --id and --layer" if options[:id] && options[:layer]
476
+ NSWTopo.scrape url, path, **options
477
+ exit
478
+ when "inspect"
479
+ url_or_path = ARGV.shift
480
+ raise OptionParser::MissingArgument, "no URL or path specified" unless url_or_path
481
+ raise OptionParser::NeedlessArgument, ARGV if ARGV.any?
482
+ [%i[where codes], %i[fields codes], %i[decode codes], %i[id layer]].each do |flags|
483
+ raise OptionParser::InvalidOption, "can't have --%s with --%s" % flags if options.values_at(*flags).all?
484
+ end
485
+ raise OptionParser::InvalidOption, "--countwise requires --fields" if options[:countwise] && !options[:fields]
486
+ raise OptionParser::InvalidOption, "--decode requires --fields" if options[:decode] && !options[:fields]
487
+ NSWTopo.inspect url_or_path, **options
488
+ exit
489
+ when "add", "relief"
490
+ raise OptionParser::InvalidOption, "can't specify both --resolution and --ppi" if options[:resolution] && options[:ppi]
391
491
  end
392
492
 
393
493
  raise OptionParser::MissingArgument, "no map path specified" if ARGV.empty?
394
494
  tgz_path = Pathname(ARGV.shift)
395
495
 
396
- begin
397
- in_path = case command
398
- when "init"
399
- raise "#{tgz_path} already exists" if !options.delete(:overwrite) && tgz_path.exist?
400
- else
401
- raise "no such file #{tgz_path}" unless tgz_path.exist?
402
- raise "#{tgz_path} is not a file" unless tgz_path.file?
403
- tgz_path
404
- end
405
-
406
- command = command.tr ?-, ?_
407
- arity, error = NSWTopo.method(command).arity, nil
496
+ in_path = case command
497
+ when "init"
498
+ raise "already a directory: #{tgz_path}" if tgz_path.directory?
499
+ raise "file already exists: #{tgz_path}" if !options.delete(:overwrite) && tgz_path.exist?
500
+ raise "no such directory: #{tgz_path.parent}" unless tgz_path.parent.directory?
501
+ else
502
+ raise "no such file: #{tgz_path}" unless tgz_path.exist?
503
+ raise "not a file: #{tgz_path}" unless tgz_path.file?
504
+ tgz_path
505
+ end
408
506
 
409
- NSWTopo::Archive.open(tgz_path, *in_path) do |archive|
410
- args = [archive, *ARGV, options]
411
- case
412
- when arity >= 0 && args.length > arity
413
- raise OptionParser::NeedlessArgument, ARGV.last(args.length - arity).join(?\s)
414
- when arity >= 0 ? args.length < arity : args.length + arity + 1 < 0
415
- raise OptionParser::MissingArgument
507
+ command = command.tr ?-, ?_
508
+ ARGV.prepend in_path.basename(".tgz").basename(".tar.gz").basename(".svg") if "render" == command
509
+
510
+ nil.tap do |partial_failure|
511
+ NSWTopo::Archive.open(in_path: in_path, out_path: tgz_path) do |archive|
512
+ NSWTopo.method(command).parameters.group_by(&:first).inject [archive] do |args, (type, params)|
513
+ case type
514
+ when :req
515
+ raise OptionParser::MissingArgument if ARGV.length < params.length - args.length
516
+ args.concat ARGV.shift(params.length - args.length)
517
+ when :rest
518
+ args.concat ARGV.shift(ARGV.length)
519
+ else args
520
+ end
521
+ end.tap do |args|
522
+ raise OptionParser::NeedlessArgument, ARGV.join(?\s) if ARGV.any?
523
+ NSWTopo.send command, *args, **options
524
+ rescue NSWTopo::PartialFailureError => partial_failure
416
525
  end
417
-
418
- NSWTopo.send command, *args
419
- rescue NSWTopo::PartialFailureError => error
420
526
  end
421
- raise error.message if error
527
+ raise partial_failure if partial_failure
528
+ rescue NSWTopo::Archive::Invalid
529
+ raise "unrecognised map file: #{in_path}" unless "render" == command
530
+ raise OptionParser::InvalidOption, "can't specify --force without a map file" if options[:force]
531
+ in_path, tgz_path, options = nil, nil, options.merge(svg_path: in_path)
532
+ retry
422
533
  end
423
534
  rescue OptionParser::ParseError => error
424
535
  warn ansi[command_parser] if $stderr.tty?
425
536
  log_abort error.message
426
537
  rescue Interrupt
427
- abort $stderr.tty? ? "\r\e[K\e[31mnswtopo:\e[0m interrupted" : "nswtopo: interrupted"
538
+ log_abort "interrupted"
428
539
  rescue RuntimeError => error
429
540
  log_abort error.message
430
541
  end
data/docs/README.md CHANGED
@@ -10,20 +10,9 @@ Help screens are available describing usage for each commands. Use the `--help`
10
10
  $ nswtopo init --help
11
11
  ```
12
12
 
13
- # Configuration
14
-
15
- An important initial step is to configure the location of *Google Chrome* on your PC. Chrome is required for rendering the map in most formats. Use the *configure* command to set the path:
16
-
17
- ```
18
- $ nswtopo config --chrome "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
19
- nswtopo: configuration updated
20
- ```
21
-
22
- Use forward slashes for paths, even on Windows.
23
-
24
13
  # Map Files
25
14
 
26
- Most commands need a map file to work on. Name this file anything you want. A `.tgz` extension is suggested, as the file is in *gzipped tar* archive format. All map contents are contained within the file, so a separate directory per map is not necessary.
15
+ Most commands need a map file to work on. Name this file anything you want. A `.tgz` extension is suggested, as the file is in *gzipped tar* archive format. All map contents are contained within the file.
27
16
 
28
17
  # Example
29
18
 
data/docs/add.md CHANGED
@@ -10,7 +10,7 @@ The forward-slash character is used indicate the nested folder structure of thes
10
10
 
11
11
  Some layers, such as vegetation layers, require a dataset to be present on your computer. Specify the location of the dataset with the `--path` option. The path can be absolute, or relative to the working directory.
12
12
 
13
- Raster layers (vegetation, shaded relief) typically have an appropriate image resolution set for that data. If desired, you can choose a different value using the `--resolution` option. Resolution is in metres per pixel, indicating the dataset quality rather than an output resolution such as pixels per inch.
13
+ Raster layers (vegetation, shaded relief) are typically imported at the data's native resolution. If desired, you can choose a different value using the `--resolution` option, for a data resolution in metres per pixel, or the `--ppi` option for output resolution in pixels per inch.
14
14
 
15
15
  For repeated use, it's easier to set the path or resolution for a layer in a permanent configuration file. Use the *config* command for this task.
16
16
 
data/docs/config.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Description
2
2
 
3
- Configure and view permanent *nswtopo* settings using the *config* command. For example, to set the *Google Chrome* path for rendering maps:
3
+ Configure and view permanent *nswtopo* settings using the *config* command. For example, to manually set the *Google Chrome* path:
4
4
 
5
5
  ```
6
6
  $ nswtopo config --chrome "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
data/docs/contours.md CHANGED
@@ -12,6 +12,8 @@ Choose a contour interval in metres using the `--interval` option. A five metre
12
12
 
13
13
  Noise in raw elevation data usually produces unsuitably rough contour lines. Some smoothing of the DEM removes most such artefacts. A default smoothing radius of 0.2mm is applied, configurable with the `--smooth` option. Increase the radius to produce smoother contours at the expense of detail.
14
14
 
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
+
15
17
  # Layer Position
16
18
 
17
19
  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:
@@ -21,7 +23,7 @@ $ nswtopo contours --replace nsw.topographic.contours map.tgz DATA_25994.zip
21
23
  ```
22
24
  # Style
23
25
 
24
- Contours are rendered in brown at a thickness of 0.08mm. Change line colour with `--stroke`, thickness with `--stroke-width` and label colour with `--fill`. Colour can be an *RGB triplet* (e.g. *800080*) or *web colour* name (e.g. *purple*).
26
+ Contours are rendered in brown at a thickness of 0.08mm. Change line colour with `--stroke`, thickness with `--stroke-width` and label colour with `--fill`. Colour can be an *RGB triplet* (e.g. *#800080*) or *web colour* name (e.g. *purple*).
25
27
 
26
28
  # Contour Thinning
27
29
  A small contour interval can produce very dense contours in steep terrain. An advanced `--thin` option is available to selectively remove contours in steep areas such as cliffsides. It emulates a manual contour thinning technique. An index multiple of eight (e.g. 5m contours with 40m index contours) produces the most aesthetic results.
data/docs/init.md CHANGED
@@ -36,3 +36,11 @@ $ nswtopo init --dimensions 210,297 --coords 148.387,-36.148 map.tgz
36
36
  # Map Scale
37
37
 
38
38
  A 1:25000 scale is conventional and should work well for most purposes. Change to a smaller or larger scale using the `--scale` option.
39
+
40
+ # Map Inset
41
+
42
+ Use the `--inset` option to remove a rectangular inset from the map. Specify the inset location as two opposing corner coordinates in millimetres. For example, to remove a 50mm × 100mm inset from the map's top-left corner:
43
+
44
+ ```
45
+ $ nswtopo init --bounds bounds.kml --inset 0,0,50,100 map.tgz
46
+ ```