nswtopo 2.0.0 → 3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/COPYING +70 -83
- data/bin/nswtopo +227 -116
- data/docs/README.md +1 -12
- data/docs/add.md +1 -1
- data/docs/config.md +1 -1
- data/docs/contours.md +3 -1
- data/docs/init.md +8 -0
- data/docs/inspect.md +103 -0
- data/docs/move.md +9 -0
- data/docs/render.md +16 -7
- data/docs/scrape.md +67 -0
- data/docs/spot-heights.md +6 -2
- data/lib/nswtopo/archive.rb +50 -41
- data/lib/nswtopo/chrome.rb +227 -0
- data/lib/nswtopo/commands/add.rb +106 -0
- data/lib/nswtopo/commands/config.rb +38 -0
- data/lib/nswtopo/commands/inspect.rb +74 -0
- data/lib/nswtopo/commands/layers.rb +22 -0
- data/lib/nswtopo/commands/scrape.rb +79 -0
- data/lib/nswtopo/commands.rb +57 -0
- data/lib/nswtopo/dither.rb +5 -3
- data/lib/nswtopo/font.rb +46 -21
- data/lib/nswtopo/formats/gemf.rb +42 -0
- data/lib/nswtopo/formats/kmz.rb +26 -24
- data/lib/nswtopo/formats/mbtiles.rb +5 -41
- data/lib/nswtopo/formats/pdf.rb +82 -17
- data/lib/nswtopo/formats/svg.rb +114 -45
- data/lib/nswtopo/formats/svgz.rb +2 -2
- data/lib/nswtopo/formats/zip.rb +33 -23
- data/lib/nswtopo/formats.rb +77 -32
- data/lib/nswtopo/geometry/overlap.rb +1 -32
- data/lib/nswtopo/geometry/r_tree.rb +16 -10
- data/lib/nswtopo/geometry/segment.rb +3 -3
- data/lib/nswtopo/geometry/straight_skeleton/nodes.rb +5 -6
- data/lib/nswtopo/geometry/vector_sequence.rb +7 -6
- data/lib/nswtopo/gis/arcgis/connection.rb +56 -0
- data/lib/nswtopo/gis/arcgis/layer/map.rb +163 -0
- data/lib/nswtopo/gis/arcgis/layer/query.rb +87 -0
- data/lib/nswtopo/gis/arcgis/layer/renderer.rb +66 -0
- data/lib/nswtopo/gis/arcgis/layer/statistics.rb +15 -0
- data/lib/nswtopo/gis/arcgis/layer.rb +201 -0
- data/lib/nswtopo/gis/arcgis/service.rb +57 -0
- data/lib/nswtopo/gis/arcgis.rb +3 -0
- data/lib/nswtopo/gis/dem.rb +13 -12
- data/lib/nswtopo/gis/esri_hdr.rb +8 -2
- data/lib/nswtopo/gis/geojson/collection.rb +45 -21
- data/lib/nswtopo/gis/geojson/multi_line_string.rb +2 -24
- data/lib/nswtopo/gis/geojson/multi_polygon.rb +2 -53
- data/lib/nswtopo/gis/geojson/polygon.rb +15 -0
- data/lib/nswtopo/gis/geojson.rb +12 -3
- data/lib/nswtopo/gis/gps/kml.rb +25 -19
- data/lib/nswtopo/gis/gps.rb +2 -0
- data/lib/nswtopo/gis/projection.rb +35 -24
- data/lib/nswtopo/gis/shapefile.rb +89 -16
- data/lib/nswtopo/gis.rb +1 -2
- data/lib/nswtopo/helpers/array.rb +0 -11
- data/lib/nswtopo/helpers/colour.rb +34 -14
- data/lib/nswtopo/layer/arcgis_raster.rb +44 -48
- data/lib/nswtopo/layer/colour_mask.rb +5 -0
- data/lib/nswtopo/layer/contour.rb +35 -28
- data/lib/nswtopo/layer/control.rb +2 -7
- data/lib/nswtopo/layer/declination.rb +9 -9
- data/lib/nswtopo/layer/feature.rb +36 -22
- data/lib/nswtopo/layer/grid.rb +30 -27
- data/lib/nswtopo/layer/import.rb +1 -21
- data/lib/nswtopo/layer/labels/barrier.rb +39 -0
- data/lib/nswtopo/layer/labels.rb +551 -383
- data/lib/nswtopo/layer/mask_render.rb +37 -0
- data/lib/nswtopo/layer/overlay.rb +2 -2
- data/lib/nswtopo/layer/raster.rb +31 -41
- data/lib/nswtopo/layer/raster_import.rb +17 -0
- data/lib/nswtopo/layer/raster_render.rb +15 -0
- data/lib/nswtopo/layer/relief.rb +27 -95
- data/lib/nswtopo/layer/spot.rb +63 -62
- data/lib/nswtopo/layer/vector/cutout.rb +15 -0
- data/lib/nswtopo/layer/vector/knockout.rb +16 -0
- data/lib/nswtopo/layer/vector.rb +121 -89
- data/lib/nswtopo/layer/vegetation.rb +39 -34
- data/lib/nswtopo/layer.rb +30 -16
- data/lib/nswtopo/map.rb +202 -109
- data/lib/nswtopo/os.rb +5 -27
- data/lib/nswtopo/tiled_web_map.rb +54 -0
- data/lib/nswtopo/tree_indenter.rb +27 -0
- data/lib/nswtopo/version.rb +27 -2
- data/lib/nswtopo.rb +6 -199
- metadata +39 -20
- data/lib/nswtopo/font/chrome.rb +0 -59
- data/lib/nswtopo/font/generic.rb +0 -25
- data/lib/nswtopo/gis/arcgis_server/connection.rb +0 -52
- data/lib/nswtopo/gis/arcgis_server.rb +0 -155
- data/lib/nswtopo/gis/geojson/multi_point.rb +0 -12
- data/lib/nswtopo/gis/world_file.rb +0 -19
- data/lib/nswtopo/layer/labels/fence.rb +0 -20
data/lib/nswtopo.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'date'
|
2
|
+
require 'time'
|
2
3
|
require 'open3'
|
3
4
|
require 'uri'
|
4
5
|
require 'net/http'
|
@@ -17,11 +18,7 @@ require 'ostruct'
|
|
17
18
|
require 'forwardable'
|
18
19
|
require 'rubygems/package'
|
19
20
|
require 'zlib'
|
20
|
-
|
21
|
-
require 'pty'
|
22
|
-
require 'expect'
|
23
|
-
rescue LoadError
|
24
|
-
end
|
21
|
+
require 'io/nonblock'
|
25
22
|
|
26
23
|
require_relative 'nswtopo/helpers'
|
27
24
|
require_relative 'nswtopo/avl_tree'
|
@@ -31,216 +28,26 @@ require_relative 'nswtopo/safely'
|
|
31
28
|
require_relative 'nswtopo/os'
|
32
29
|
require_relative 'nswtopo/dither'
|
33
30
|
require_relative 'nswtopo/zip'
|
31
|
+
require_relative 'nswtopo/chrome'
|
34
32
|
require_relative 'nswtopo/font'
|
35
33
|
require_relative 'nswtopo/archive'
|
36
34
|
require_relative 'nswtopo/gis'
|
35
|
+
require_relative 'nswtopo/tiled_web_map'
|
37
36
|
require_relative 'nswtopo/formats'
|
38
37
|
require_relative 'nswtopo/map'
|
39
38
|
require_relative 'nswtopo/layer'
|
40
39
|
require_relative 'nswtopo/version'
|
41
40
|
require_relative 'nswtopo/config'
|
41
|
+
require_relative 'nswtopo/commands'
|
42
|
+
require_relative 'nswtopo/tree_indenter'
|
42
43
|
|
43
44
|
module NSWTopo
|
44
45
|
PartialFailureError = Class.new RuntimeError
|
45
46
|
extend self, Log
|
46
47
|
|
47
|
-
def init(archive, options)
|
48
|
-
puts Map.init(archive, options)
|
49
|
-
end
|
50
|
-
|
51
48
|
def layer_dirs
|
52
49
|
@layer_dirs ||= Array(Config["layer-dir"]).map(&Pathname.method(:new)) << Pathname.pwd
|
53
50
|
end
|
54
|
-
|
55
|
-
def info(archive, options)
|
56
|
-
puts Map.load(archive).info(options)
|
57
|
-
end
|
58
|
-
|
59
|
-
def add(archive, *layers, options)
|
60
|
-
create_options = {
|
61
|
-
after: Layer.sanitise(options.delete :after),
|
62
|
-
before: Layer.sanitise(options.delete :before),
|
63
|
-
replace: Layer.sanitise(options.delete :replace),
|
64
|
-
overwrite: options.delete(:overwrite)
|
65
|
-
}
|
66
|
-
map = Map.load archive
|
67
|
-
|
68
|
-
Enumerator.new do |yielder|
|
69
|
-
while layers.any?
|
70
|
-
layer, basedir = layers.shift
|
71
|
-
path = Pathname(layer).expand_path(*basedir)
|
72
|
-
case layer
|
73
|
-
when /^controls\.(gpx|kml)$/i
|
74
|
-
yielder << [path.basename(path.extname).to_s, "type" => "Control", "path" => path]
|
75
|
-
when /\.(gpx|kml)$/i
|
76
|
-
yielder << [path.basename(path.extname).to_s, "type" => "Overlay", "path" => path]
|
77
|
-
when /\.(tiff?|png|jpg)$/i
|
78
|
-
yielder << [path.basename(path.extname).to_s, "type" => "Import", "path" => path]
|
79
|
-
when "contours"
|
80
|
-
yielder << [layer, "type" => "Contour"]
|
81
|
-
when "spot-heights"
|
82
|
-
yielder << [layer, "type" => "Spot"]
|
83
|
-
when "relief"
|
84
|
-
yielder << [layer, "type" => "Relief"]
|
85
|
-
when "grid"
|
86
|
-
yielder << [layer, "type" => "Grid"]
|
87
|
-
when "declination"
|
88
|
-
yielder << [layer, "type" => "Declination"]
|
89
|
-
when "controls"
|
90
|
-
yielder << [layer, "type" => "Control"]
|
91
|
-
when /\.yml$/i
|
92
|
-
basedir ||= path.parent
|
93
|
-
raise "couldn't find '#{layer}'" unless path.file?
|
94
|
-
case contents = YAML.load(path.read)
|
95
|
-
when Array
|
96
|
-
contents.reverse.map do |item|
|
97
|
-
Pathname(item.to_s)
|
98
|
-
end.each do |relative_path|
|
99
|
-
raise "#{relative_path} is not a relative path" unless relative_path.relative?
|
100
|
-
layers.prepend [Pathname(relative_path).expand_path(path.parent).relative_path_from(basedir).to_s, basedir]
|
101
|
-
end
|
102
|
-
when Hash
|
103
|
-
name = path.sub_ext("").relative_path_from(basedir).descend.map(&:basename).join(?.)
|
104
|
-
yielder << [name, contents.merge("source" => path)]
|
105
|
-
else
|
106
|
-
raise "couldn't parse #{path}"
|
107
|
-
end
|
108
|
-
else
|
109
|
-
path = Pathname("#{layer}.yml")
|
110
|
-
raise "#{layer} is not a relative path" unless path.relative?
|
111
|
-
basedir ||= layer_dirs.find do |root|
|
112
|
-
path.expand_path(root).file?
|
113
|
-
end
|
114
|
-
layers.prepend [path.to_s, basedir]
|
115
|
-
end
|
116
|
-
end
|
117
|
-
rescue YAML::Exception
|
118
|
-
raise "couldn't parse #{path}"
|
119
|
-
end.map do |name, params|
|
120
|
-
params.merge! options.transform_keys(&:to_s)
|
121
|
-
params.merge! Config[name] if Config[name]
|
122
|
-
Layer.new(name, map, params)
|
123
|
-
end.tap do |layers|
|
124
|
-
raise OptionParser::MissingArgument, "no layers specified" unless layers.any?
|
125
|
-
unless layers.one?
|
126
|
-
raise OptionParser::InvalidArgument, "can't specify resolution when adding multiple layers" if options[:resolution]
|
127
|
-
raise OptionParser::InvalidArgument, "can't specify data path when adding multiple layers" if options[:path]
|
128
|
-
end
|
129
|
-
map.add *layers, create_options
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
def contours(archive, dem_path, options)
|
134
|
-
add archive, "contours", options.merge(path: Pathname(dem_path))
|
135
|
-
end
|
136
|
-
|
137
|
-
def spot_heights(archive, dem_path, options)
|
138
|
-
add archive, "spot-heights", options.merge(path: Pathname(dem_path))
|
139
|
-
end
|
140
|
-
|
141
|
-
def relief(archive, dem_path, options)
|
142
|
-
add archive, "relief", options.merge(path: Pathname(dem_path))
|
143
|
-
end
|
144
|
-
|
145
|
-
def grid(archive, options)
|
146
|
-
add archive, "grid", options
|
147
|
-
end
|
148
|
-
|
149
|
-
def declination(archive, options)
|
150
|
-
add archive, "declination", options
|
151
|
-
end
|
152
|
-
|
153
|
-
def controls(archive, gps_path, options)
|
154
|
-
add archive, "controls", options.merge(path: Pathname(gps_path))
|
155
|
-
end
|
156
|
-
|
157
|
-
def overlay(archive, gps_path, options)
|
158
|
-
raise OptionParser::InvalidArgument, gps_path unless gps_path =~ /\.(gpx|kml)$/i
|
159
|
-
add archive, gps_path, options.merge(path: Pathname(gps_path))
|
160
|
-
end
|
161
|
-
|
162
|
-
def delete(archive, *names, options)
|
163
|
-
map = Map.load archive
|
164
|
-
names.map do |name|
|
165
|
-
Layer.sanitise name
|
166
|
-
end.uniq.map do |name|
|
167
|
-
name[?*] ? %r[^#{name.gsub(?., '\.').gsub(?*, '.*')}$] : name
|
168
|
-
end.tap do |names|
|
169
|
-
map.delete *names
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
def render(archive, *formats, options)
|
174
|
-
overwrite = options.delete :overwrite
|
175
|
-
formats << "svg" if formats.empty?
|
176
|
-
formats.map do |format|
|
177
|
-
Pathname(Formats === format ? "#{archive.basename}.#{format}" : format)
|
178
|
-
end.uniq.each do |path|
|
179
|
-
format = path.extname.delete_prefix(?.)
|
180
|
-
raise "unrecognised format: #{path}" if format.empty?
|
181
|
-
raise "unrecognised format: #{format}" unless Formats === format
|
182
|
-
raise "file already exists: #{path}" if path.exist? && !overwrite
|
183
|
-
raise "non-existent directory: #{path.parent}" unless path.parent.directory?
|
184
|
-
end.tap do |paths|
|
185
|
-
Map.load(archive).render *paths, options
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
def layers(state: nil, indent: "")
|
190
|
-
layer_dirs.grep_v(Pathname.pwd).flat_map do |directory|
|
191
|
-
Array(state).inject(directory, &:/).glob("*")
|
192
|
-
end.sort.each do |path|
|
193
|
-
case
|
194
|
-
when path.directory?
|
195
|
-
next if path.glob("**/*.yml").none?
|
196
|
-
puts [indent, path.basename.sub_ext("")].join
|
197
|
-
layers state: [*state, path.basename], indent: " " + indent
|
198
|
-
when path.sub_ext("").directory?
|
199
|
-
when path.extname == ".yml"
|
200
|
-
puts [indent, path.basename.sub_ext("")].join
|
201
|
-
end
|
202
|
-
end.tap do |paths|
|
203
|
-
log_warn "no layers installed" if paths.none?
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
def config(layer = nil, **options)
|
208
|
-
chrome, firefox, path, resolution, layer_dir, labelling, delete = options.values_at :chrome, :firefox, :path, :resolution, :"layer-dir", :labelling, :delete
|
209
|
-
raise "not a directory: %s" % layer_dir if layer_dir && !layer_dir.directory?
|
210
|
-
raise "chrome path is not an executable" if chrome && !chrome.executable?
|
211
|
-
raise "firefox path is not an executable" if firefox && !firefox.executable?
|
212
|
-
Config.store("chrome", chrome.to_s) if chrome
|
213
|
-
Config.store("firefox", firefox.to_s) if firefox
|
214
|
-
Config.store("labelling", labelling) unless labelling.nil?
|
215
|
-
Config.store("layer-dir", layer_dir.to_s) if layer_dir
|
216
|
-
|
217
|
-
layer = Layer.sanitise layer
|
218
|
-
case
|
219
|
-
when !layer
|
220
|
-
raise OptionParser::InvalidArgument, "no layer name specified for path" if path
|
221
|
-
raise OptionParser::InvalidArgument, "no layer name specified for resolution" if resolution
|
222
|
-
when path || resolution
|
223
|
-
Config.store(layer, "path", path.to_s) if path
|
224
|
-
Config.store(layer, "resolution", resolution) if resolution
|
225
|
-
end
|
226
|
-
Config.delete(*layer, delete) if delete
|
227
|
-
|
228
|
-
if options.empty?
|
229
|
-
puts Config.to_str.each_line.drop(1)
|
230
|
-
log_neutral "no configuration yet" if Config.empty?
|
231
|
-
else
|
232
|
-
Config.save
|
233
|
-
log_success "configuration updated"
|
234
|
-
end
|
235
|
-
end
|
236
|
-
|
237
|
-
def with_browser
|
238
|
-
browser_name, browser_path = Config.slice("chrome", "firefox").first
|
239
|
-
raise "please configure a path for google chrome" unless browser_name
|
240
|
-
yield browser_name, Pathname.new(browser_path)
|
241
|
-
rescue Errno::ENOENT
|
242
|
-
raise "invalid %s path: %s" % [browser_name, browser_path]
|
243
|
-
end
|
244
51
|
end
|
245
52
|
|
246
53
|
begin
|
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nswtopo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: '3.0'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew Hollingworth
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-08-03 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description:
|
14
|
-
email:
|
13
|
+
description:
|
14
|
+
email:
|
15
15
|
executables:
|
16
16
|
- nswtopo
|
17
17
|
extensions: []
|
@@ -29,20 +29,29 @@ files:
|
|
29
29
|
- docs/grid.md
|
30
30
|
- docs/info.md
|
31
31
|
- docs/init.md
|
32
|
+
- docs/inspect.md
|
32
33
|
- docs/layers.md
|
34
|
+
- docs/move.md
|
33
35
|
- docs/overlay.md
|
34
36
|
- docs/relief.md
|
35
37
|
- docs/render.md
|
38
|
+
- docs/scrape.md
|
36
39
|
- docs/spot-heights.md
|
37
40
|
- lib/nswtopo.rb
|
38
41
|
- lib/nswtopo/archive.rb
|
39
42
|
- lib/nswtopo/avl_tree.rb
|
43
|
+
- lib/nswtopo/chrome.rb
|
44
|
+
- lib/nswtopo/commands.rb
|
45
|
+
- lib/nswtopo/commands/add.rb
|
46
|
+
- lib/nswtopo/commands/config.rb
|
47
|
+
- lib/nswtopo/commands/inspect.rb
|
48
|
+
- lib/nswtopo/commands/layers.rb
|
49
|
+
- lib/nswtopo/commands/scrape.rb
|
40
50
|
- lib/nswtopo/config.rb
|
41
51
|
- lib/nswtopo/dither.rb
|
42
52
|
- lib/nswtopo/font.rb
|
43
|
-
- lib/nswtopo/font/chrome.rb
|
44
|
-
- lib/nswtopo/font/generic.rb
|
45
53
|
- lib/nswtopo/formats.rb
|
54
|
+
- lib/nswtopo/formats/gemf.rb
|
46
55
|
- lib/nswtopo/formats/kmz.rb
|
47
56
|
- lib/nswtopo/formats/mbtiles.rb
|
48
57
|
- lib/nswtopo/formats/pdf.rb
|
@@ -63,8 +72,14 @@ files:
|
|
63
72
|
- lib/nswtopo/geometry/vector.rb
|
64
73
|
- lib/nswtopo/geometry/vector_sequence.rb
|
65
74
|
- lib/nswtopo/gis.rb
|
66
|
-
- lib/nswtopo/gis/
|
67
|
-
- lib/nswtopo/gis/
|
75
|
+
- lib/nswtopo/gis/arcgis.rb
|
76
|
+
- lib/nswtopo/gis/arcgis/connection.rb
|
77
|
+
- lib/nswtopo/gis/arcgis/layer.rb
|
78
|
+
- lib/nswtopo/gis/arcgis/layer/map.rb
|
79
|
+
- lib/nswtopo/gis/arcgis/layer/query.rb
|
80
|
+
- lib/nswtopo/gis/arcgis/layer/renderer.rb
|
81
|
+
- lib/nswtopo/gis/arcgis/layer/statistics.rb
|
82
|
+
- lib/nswtopo/gis/arcgis/service.rb
|
68
83
|
- lib/nswtopo/gis/dem.rb
|
69
84
|
- lib/nswtopo/gis/esri_hdr.rb
|
70
85
|
- lib/nswtopo/gis/gdal_glob.rb
|
@@ -72,7 +87,6 @@ files:
|
|
72
87
|
- lib/nswtopo/gis/geojson/collection.rb
|
73
88
|
- lib/nswtopo/gis/geojson/line_string.rb
|
74
89
|
- lib/nswtopo/gis/geojson/multi_line_string.rb
|
75
|
-
- lib/nswtopo/gis/geojson/multi_point.rb
|
76
90
|
- lib/nswtopo/gis/geojson/multi_polygon.rb
|
77
91
|
- lib/nswtopo/gis/geojson/point.rb
|
78
92
|
- lib/nswtopo/gis/geojson/polygon.rb
|
@@ -81,7 +95,6 @@ files:
|
|
81
95
|
- lib/nswtopo/gis/gps/kml.rb
|
82
96
|
- lib/nswtopo/gis/projection.rb
|
83
97
|
- lib/nswtopo/gis/shapefile.rb
|
84
|
-
- lib/nswtopo/gis/world_file.rb
|
85
98
|
- lib/nswtopo/help_formatter.rb
|
86
99
|
- lib/nswtopo/helpers.rb
|
87
100
|
- lib/nswtopo/helpers/array.rb
|
@@ -92,6 +105,7 @@ files:
|
|
92
105
|
- lib/nswtopo/helpers/tar_writer.rb
|
93
106
|
- lib/nswtopo/layer.rb
|
94
107
|
- lib/nswtopo/layer/arcgis_raster.rb
|
108
|
+
- lib/nswtopo/layer/colour_mask.rb
|
95
109
|
- lib/nswtopo/layer/contour.rb
|
96
110
|
- lib/nswtopo/layer/control.rb
|
97
111
|
- lib/nswtopo/layer/declination.rb
|
@@ -99,24 +113,31 @@ files:
|
|
99
113
|
- lib/nswtopo/layer/grid.rb
|
100
114
|
- lib/nswtopo/layer/import.rb
|
101
115
|
- lib/nswtopo/layer/labels.rb
|
102
|
-
- lib/nswtopo/layer/labels/
|
116
|
+
- lib/nswtopo/layer/labels/barrier.rb
|
117
|
+
- lib/nswtopo/layer/mask_render.rb
|
103
118
|
- lib/nswtopo/layer/overlay.rb
|
104
119
|
- lib/nswtopo/layer/raster.rb
|
120
|
+
- lib/nswtopo/layer/raster_import.rb
|
121
|
+
- lib/nswtopo/layer/raster_render.rb
|
105
122
|
- lib/nswtopo/layer/relief.rb
|
106
123
|
- lib/nswtopo/layer/spot.rb
|
107
124
|
- lib/nswtopo/layer/vector.rb
|
125
|
+
- lib/nswtopo/layer/vector/cutout.rb
|
126
|
+
- lib/nswtopo/layer/vector/knockout.rb
|
108
127
|
- lib/nswtopo/layer/vegetation.rb
|
109
128
|
- lib/nswtopo/log.rb
|
110
129
|
- lib/nswtopo/map.rb
|
111
130
|
- lib/nswtopo/os.rb
|
112
131
|
- lib/nswtopo/safely.rb
|
132
|
+
- lib/nswtopo/tiled_web_map.rb
|
133
|
+
- lib/nswtopo/tree_indenter.rb
|
113
134
|
- lib/nswtopo/version.rb
|
114
135
|
- lib/nswtopo/zip.rb
|
115
136
|
homepage: https://github.com/mholling/nswtopo
|
116
137
|
licenses:
|
117
138
|
- GPL-3.0
|
118
139
|
metadata: {}
|
119
|
-
post_install_message:
|
140
|
+
post_install_message:
|
120
141
|
rdoc_options: []
|
121
142
|
require_paths:
|
122
143
|
- lib
|
@@ -124,19 +145,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
124
145
|
requirements:
|
125
146
|
- - ">="
|
126
147
|
- !ruby/object:Gem::Version
|
127
|
-
version:
|
148
|
+
version: 3.0.4
|
128
149
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
150
|
requirements:
|
130
151
|
- - ">="
|
131
152
|
- !ruby/object:Gem::Version
|
132
153
|
version: '0'
|
133
154
|
requirements:
|
134
|
-
- GDAL >=
|
135
|
-
-
|
136
|
-
|
137
|
-
|
138
|
-
rubygems_version: 2.7.6
|
139
|
-
signing_key:
|
155
|
+
- GDAL >= v3.4
|
156
|
+
- Google Chrome >= v112
|
157
|
+
rubygems_version: 3.2.33
|
158
|
+
signing_key:
|
140
159
|
specification_version: 4
|
141
160
|
summary: A vector topographic mapping tool
|
142
161
|
test_files: []
|
data/lib/nswtopo/font/chrome.rb
DELETED
@@ -1,59 +0,0 @@
|
|
1
|
-
module NSWTopo
|
2
|
-
module Font
|
3
|
-
module Chrome
|
4
|
-
ATTRIBUTES = %w[font-family font-variant font-style font-weight font-size letter-spacing word-spacing]
|
5
|
-
|
6
|
-
def command(string)
|
7
|
-
@input.puts string
|
8
|
-
lines, match = @output.expect(/(\{.*)\n/, 1)
|
9
|
-
response = JSON.parse match
|
10
|
-
raise "unexpected chrome error: %s" % response.dig("exceptionDetails", "exception", "description") if response["exceptionDetails"]
|
11
|
-
response.fetch("result").dig("value")
|
12
|
-
rescue TypeError, JSON::ParserError, KeyError
|
13
|
-
raise "unexpected chrome error"
|
14
|
-
end
|
15
|
-
|
16
|
-
def start_chrome
|
17
|
-
chrome_path = Config["chrome"]
|
18
|
-
svg = <<~XML
|
19
|
-
<?xml version='1.0' encoding='UTF-8'?>
|
20
|
-
<svg version='1.1' baseProfile='full' xmlns='http://www.w3.org/2000/svg' width='1mm' height='1mm' viewBox='0 0 1 1'>
|
21
|
-
<rect id='mm' width='1' height='1' stroke='none' />
|
22
|
-
<text id='text' />
|
23
|
-
</svg>
|
24
|
-
XML
|
25
|
-
@output, @input, @pid = PTY.spawn chrome_path, "--headless", "--disable-gpu", "--repl", "data:image/svg+xml;base64,#{Base64.encode64 svg}"
|
26
|
-
ObjectSpace.define_finalizer self, Proc.new { @input.puts "quit" }
|
27
|
-
command %Q[text = document.getElementById("text")]
|
28
|
-
@mm = command %Q[document.getElementById("mm").getBoundingClientRect().width]
|
29
|
-
end
|
30
|
-
|
31
|
-
def self.extended(instance)
|
32
|
-
instance.start_chrome
|
33
|
-
end
|
34
|
-
|
35
|
-
def validate(family)
|
36
|
-
return unless family
|
37
|
-
@families ||= Set[]
|
38
|
-
@families.add?(family) || return
|
39
|
-
command %Q[text.textContent="abcdefghijklmnopqrstuvwxyz"]
|
40
|
-
["font-family:#{family}", nil].map do |style|
|
41
|
-
command %Q[text.setAttribute("style", "#{style}")]
|
42
|
-
command %Q[text.getBoundingClientRect().width]
|
43
|
-
end.inject(&:==) || return
|
44
|
-
log_neutral "font '#{family}' doesn't appear to be available"
|
45
|
-
end
|
46
|
-
|
47
|
-
def glyph_length(string, attributes)
|
48
|
-
style = attributes.slice(*ATTRIBUTES).map do |pair|
|
49
|
-
pair.join ?:
|
50
|
-
end.join(?;)
|
51
|
-
style << ";white-space:pre" if ?\s == string
|
52
|
-
validate attributes["font-family"]
|
53
|
-
command %Q[text.setAttribute("style", #{style.inspect})]
|
54
|
-
command %Q[text.textContent=#{string.inspect}]
|
55
|
-
command(%Q[text.getBoundingClientRect().width]) / @mm
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
data/lib/nswtopo/font/generic.rb
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
module NSWTopo
|
2
|
-
module Font
|
3
|
-
module Generic
|
4
|
-
WIDTHS = {
|
5
|
-
?A => 0.732, ?B => 0.678, ?C => 0.682, ?D => 0.740, ?E => 0.583, ?F => 0.558, ?G => 0.728, ?H => 0.761, ?I => 0.256, ?J => 0.331, ?K => 0.641, ?L => 0.542, ?M => 0.843,
|
6
|
-
?N => 0.740, ?O => 0.769, ?P => 0.649, ?Q => 0.769, ?R => 0.690, ?S => 0.620, ?T => 0.599, ?U => 0.728, ?V => 0.695, ?W => 1.108, ?X => 0.649, ?Y => 0.637, ?Z => 0.591,
|
7
|
-
?a => 0.595, ?b => 0.595, ?c => 0.492, ?d => 0.595, ?e => 0.542, ?f => 0.335, ?g => 0.599, ?h => 0.583, ?i => 0.236, ?j => 0.289, ?k => 0.521, ?l => 0.236, ?m => 0.876,
|
8
|
-
?n => 0.583, ?o => 0.571, ?p => 0.595, ?q => 0.595, ?r => 0.360, ?s => 0.492, ?t => 0.347, ?u => 0.575, ?v => 0.529, ?w => 0.864, ?x => 0.533, ?y => 0.529, ?z => 0.513,
|
9
|
-
?0 => 0.595, ?1 => 0.595, ?2 => 0.595, ?3 => 0.595, ?4 => 0.595, ?5 => 0.595, ?6 => 0.595, ?7 => 0.595, ?8 => 0.595, ?9 => 0.595, ?! => 0.227, ?" => 0.422, ?# => 0.604,
|
10
|
-
?$ => 0.595, ?% => 0.934, ?& => 0.678, ?' => 0.219, ?( => 0.314, ?) => 0.314, ?* => 0.451, ?+ => 0.595, ?, => 0.227, ?- => 0.426, ?. => 0.227, ?/ => 0.331, ?\\ => 0.327,
|
11
|
-
?[ => 0.314, ?] => 0.314, ?^ => 0.595, ?_ => 0.500, ?` => 0.310, ?: => 0.227, ?; => 0.227, ?< => 0.595, ?= => 0.595, ?> => 0.595, ?? => 0.442, ?@ => 0.930, ?\s => 0.265,
|
12
|
-
}
|
13
|
-
WIDTHS.default = WIDTHS[?M]
|
14
|
-
|
15
|
-
def glyph_length(string, attributes)
|
16
|
-
font_size, letter_spacing, word_spacing = attributes.values_at("font-size", "letter-spacing", "word-spacing").map(&:to_f)
|
17
|
-
string.chars.each_cons(2).inject(WIDTHS[string[0]] * font_size) do |sum, pair|
|
18
|
-
next sum + WIDTHS[pair[1]] * font_size + letter_spacing unless pair[0] == ?\s
|
19
|
-
next sum + WIDTHS[pair[1]] * font_size + letter_spacing + word_spacing unless pair[1] == ?\s
|
20
|
-
sum
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
@@ -1,52 +0,0 @@
|
|
1
|
-
module NSWTopo
|
2
|
-
module ArcGISServer
|
3
|
-
class Connection
|
4
|
-
def initialize(http, service_path)
|
5
|
-
@http, @service_path, @headers = http, service_path, { "User-Agent" => "Ruby/#{RUBY_VERSION}", "Referer" => "%s://%s" % [http.use_ssl? ? "https" : "http", http.address] }
|
6
|
-
http.max_retries = 0
|
7
|
-
end
|
8
|
-
|
9
|
-
def repeatedly_request(request)
|
10
|
-
intervals ||= 5.times.map(&1.4142.method(:**))
|
11
|
-
response = @http.request(request)
|
12
|
-
response.error! unless Net::HTTPSuccess === response
|
13
|
-
yield response
|
14
|
-
rescue *ERRORS, Error => error
|
15
|
-
interval = intervals.shift
|
16
|
-
interval ? sleep(interval) : raise(error)
|
17
|
-
retry
|
18
|
-
end
|
19
|
-
|
20
|
-
def get(relative_path, **query, &block)
|
21
|
-
path = Pathname(@service_path).join(relative_path).to_s
|
22
|
-
path << ?? << URI.encode_www_form(query) unless query.empty?
|
23
|
-
request = Net::HTTP::Get.new(path, @headers)
|
24
|
-
repeatedly_request(request, &block)
|
25
|
-
end
|
26
|
-
|
27
|
-
def post(relative_path, **query, &block)
|
28
|
-
path = Pathname(@service_path).join(relative_path).to_s
|
29
|
-
request = Net::HTTP::Post.new(path, @headers)
|
30
|
-
request.body = URI.encode_www_form(query)
|
31
|
-
repeatedly_request(request, &block)
|
32
|
-
end
|
33
|
-
|
34
|
-
def process_json(response)
|
35
|
-
JSON.parse(response.body).tap do |result|
|
36
|
-
# raise Error, result["error"].values_at("message", "details").compact.join(?\n) if result["error"]
|
37
|
-
raise Error, result["error"]["message"] if result["error"]
|
38
|
-
end
|
39
|
-
rescue JSON::ParserError
|
40
|
-
raise Error, "unexpected ArcGIS response format"
|
41
|
-
end
|
42
|
-
|
43
|
-
def get_json(relative_path = "", **query)
|
44
|
-
get relative_path, query.merge(f: "json"), &method(:process_json)
|
45
|
-
end
|
46
|
-
|
47
|
-
def post_json(relative_path = "", **query)
|
48
|
-
post relative_path, query.merge(f: "json"), &method(:process_json)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
@@ -1,155 +0,0 @@
|
|
1
|
-
require_relative 'arcgis_server/connection'
|
2
|
-
|
3
|
-
module NSWTopo
|
4
|
-
module ArcGISServer
|
5
|
-
Error = Class.new RuntimeError
|
6
|
-
ERRORS = [Timeout::Error, Errno::ENETUNREACH, Errno::ETIMEDOUT, Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, SocketError]
|
7
|
-
SERVICE = /^(?:MapServer|FeatureServer|ImageServer)$/
|
8
|
-
|
9
|
-
def self.check_uri(url)
|
10
|
-
uri = URI.parse url
|
11
|
-
return unless URI::HTTP === uri
|
12
|
-
instance, (id, *) = uri.path.split(?/).slice_after(SERVICE).take(2)
|
13
|
-
return unless instance.last =~ SERVICE
|
14
|
-
return unless !id || id =~ /^\d+$/
|
15
|
-
return uri, instance.join(?/), id
|
16
|
-
rescue URI::Error
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.===(string)
|
20
|
-
uri, service_path, id = check_uri string
|
21
|
-
uri != nil
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.start(url, &block)
|
25
|
-
uri, service_path, id = check_uri url
|
26
|
-
raise "invalid ArcGIS server URL: %s" % url unless uri
|
27
|
-
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", read_timeout: 600) do |http|
|
28
|
-
connection = Connection.new http, service_path
|
29
|
-
service = connection.get_json
|
30
|
-
projection = case
|
31
|
-
when wkt = service.dig("spatialReference", "wkt") then Projection.new(wkt)
|
32
|
-
when wkid = service.dig("spatialReference", "latestWkid") then Projection.new("EPSG:#{wkid}")
|
33
|
-
when wkid = service.dig("spatialReference", "wkid") then Projection.new("EPSG:#{wkid == 102100 ? 3857 : wkid}")
|
34
|
-
else raise Error, "no spatial reference found: #{uri}"
|
35
|
-
end
|
36
|
-
yield connection, service, projection, *id
|
37
|
-
end
|
38
|
-
rescue *ERRORS => error
|
39
|
-
raise Error, error.message
|
40
|
-
end
|
41
|
-
|
42
|
-
def arcgis_layer(url, where: nil, layer: nil, per_page: nil, margin: {})
|
43
|
-
ArcGISServer.start url do |connection, service, projection, id|
|
44
|
-
id = service["layers"].find do |info|
|
45
|
-
layer.to_s == info["name"]
|
46
|
-
end&.dig("id") if layer
|
47
|
-
id ? nil : layer ? raise("no such ArcGIS layer: %s" % layer) : raise("not an ArcGIS layer url: %s" % url)
|
48
|
-
|
49
|
-
layer = connection.get_json id.to_s
|
50
|
-
query_path = "#{id}/query"
|
51
|
-
max_record_count, fields, types, type_id_field, geometry_type, capabilities = layer.values_at "maxRecordCount", "fields", "types", "typeIdField", "geometryType", "capabilities"
|
52
|
-
raise Error, "no query capability available: #{url}" unless capabilities =~ /Query|Data/
|
53
|
-
|
54
|
-
if type_id_field && types
|
55
|
-
type_id_field = fields.find do |field|
|
56
|
-
field.values_at("alias", "name").include? type_id_field
|
57
|
-
end&.fetch("name")
|
58
|
-
type_values = types.map do |type|
|
59
|
-
type.values_at "id", "name"
|
60
|
-
end.to_h
|
61
|
-
subtype_coded_values = types.map do |type|
|
62
|
-
type.values_at "id", "domains"
|
63
|
-
end.map do |id, domains|
|
64
|
-
coded_values = domains.map do |name, domain|
|
65
|
-
[name, domain["codedValues"]]
|
66
|
-
end.select(&:last).map do |name, pairs|
|
67
|
-
values = pairs.map do |pair|
|
68
|
-
pair.values_at "code", "name"
|
69
|
-
end.to_h
|
70
|
-
[name, values]
|
71
|
-
end.to_h
|
72
|
-
[id, coded_values]
|
73
|
-
end.to_h
|
74
|
-
end
|
75
|
-
|
76
|
-
coded_values = fields.map do |field|
|
77
|
-
[field["name"], field.dig("domain", "codedValues")]
|
78
|
-
end.select(&:last).map do |name, pairs|
|
79
|
-
values = pairs.map do |pair|
|
80
|
-
pair.values_at "code", "name"
|
81
|
-
end.to_h
|
82
|
-
[name, values]
|
83
|
-
end.to_h
|
84
|
-
|
85
|
-
geometry = { rings: @map.bounding_box(margin).reproject_to(projection).coordinates.map(&:reverse) }.to_json
|
86
|
-
where = Array(where).map { |clause| "(#{clause})"}.join " AND "
|
87
|
-
query = { geometry: geometry, geometryType: "esriGeometryPolygon", returnIdsOnly: true, where: where }
|
88
|
-
|
89
|
-
object_ids = connection.get_json(query_path, query)["objectIds"]
|
90
|
-
next GeoJSON::Collection.new projection unless object_ids
|
91
|
-
|
92
|
-
features = Enumerator.new do |yielder|
|
93
|
-
per_page, total = [*per_page, *max_record_count, 500].min, object_ids.length
|
94
|
-
while object_ids.any?
|
95
|
-
yield total - object_ids.length, total if block_given? && total > 0
|
96
|
-
yielder << begin
|
97
|
-
connection.get_json query_path, outFields: ?*, objectIds: object_ids.take(per_page).join(?,)
|
98
|
-
rescue Error => error
|
99
|
-
(per_page /= 2) > 0 ? retry : raise(error)
|
100
|
-
end
|
101
|
-
object_ids.shift per_page
|
102
|
-
end
|
103
|
-
end.inject [] do |features, page|
|
104
|
-
features += page["features"]
|
105
|
-
end.map do |feature|
|
106
|
-
next unless geometry = feature["geometry"]
|
107
|
-
attributes = feature.fetch "attributes", {}
|
108
|
-
|
109
|
-
values = attributes.map do |name, value|
|
110
|
-
case
|
111
|
-
when type_id_field == name
|
112
|
-
type_values[value]
|
113
|
-
when decode = subtype_coded_values&.dig(attributes[type_id_field], name)
|
114
|
-
decode[value]
|
115
|
-
when decode = coded_values.dig(name)
|
116
|
-
decode[value]
|
117
|
-
when %w[null Null NULL <null> <Null> <NULL>].include?(value)
|
118
|
-
nil
|
119
|
-
else value
|
120
|
-
end
|
121
|
-
end
|
122
|
-
attributes = attributes.keys.zip(values).to_h
|
123
|
-
|
124
|
-
case geometry_type
|
125
|
-
when "esriGeometryPoint"
|
126
|
-
point = geometry.values_at "x", "y"
|
127
|
-
next unless point.all?
|
128
|
-
next GeoJSON::Point.new point, attributes
|
129
|
-
when "esriGeometryMultipoint"
|
130
|
-
points = geometry["points"]
|
131
|
-
next unless points&.any?
|
132
|
-
next GeoJSON::MultiPoint.new points.transpose.take(2).transpose, attributes
|
133
|
-
when "esriGeometryPolyline"
|
134
|
-
raise Error, "ArcGIS curve geometries not supported" if geometry.key? "curvePaths"
|
135
|
-
paths = geometry["paths"]
|
136
|
-
next unless paths&.any?
|
137
|
-
next GeoJSON::LineString.new paths[0], attributes if paths.one?
|
138
|
-
next GeoJSON::MultiLineString.new paths, attributes
|
139
|
-
when "esriGeometryPolygon"
|
140
|
-
raise Error, "ArcGIS curve geometries not supported" if geometry.key? "curveRings"
|
141
|
-
rings = geometry["rings"]
|
142
|
-
next unless rings&.any?
|
143
|
-
rings.each(&:reverse!) unless rings[0].anticlockwise?
|
144
|
-
next GeoJSON::Polygon.new rings, attributes if rings.one?
|
145
|
-
next GeoJSON::MultiPolygon.new rings.slice_before(&:anticlockwise?).to_a, attributes
|
146
|
-
else
|
147
|
-
raise Error, "unsupported ArcGIS geometry type: #{geometry_type}"
|
148
|
-
end
|
149
|
-
end.compact
|
150
|
-
|
151
|
-
GeoJSON::Collection.new projection, features
|
152
|
-
end
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|