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
@@ -6,22 +6,17 @@ module NSWTopo
|
|
6
6
|
diameter: 7.0
|
7
7
|
colour: darkmagenta
|
8
8
|
stroke-width: 0.25
|
9
|
+
knockout: 0.1
|
9
10
|
waterdrop:
|
10
11
|
stroke: blue
|
11
12
|
labels:
|
12
|
-
dupe: outline
|
13
|
-
outline:
|
14
|
-
stroke: white
|
15
|
-
fill: none
|
16
|
-
stroke-width: 0.25
|
17
|
-
stroke-opacity: 0.75
|
18
13
|
position: [ aboveright, belowright, aboveleft, belowleft, right, left, above, below ]
|
19
14
|
font-family: sans-serif
|
20
15
|
font-style: normal
|
21
16
|
stroke: none
|
22
17
|
YAML
|
23
18
|
SCALING_PARAMS = <<~YAML
|
24
|
-
|
19
|
+
barrier: 2.0
|
25
20
|
control:
|
26
21
|
symbol:
|
27
22
|
- circle:
|
@@ -17,23 +17,23 @@ module NSWTopo
|
|
17
17
|
def get_features
|
18
18
|
@params["fill"] ||= @params["stroke"]
|
19
19
|
declination = @angle || @map.declination
|
20
|
-
col_spacing =
|
21
|
-
row_spacing =
|
22
|
-
col_offset =
|
20
|
+
col_spacing = @spacing
|
21
|
+
row_spacing = @arrows * 0.5
|
22
|
+
col_offset = @offset % @spacing
|
23
23
|
|
24
|
-
radius = 0.5 * @map.bounds.transpose.distance
|
24
|
+
radius = 0.5 * @map.neatline.bounds.transpose.distance
|
25
25
|
j_max = (radius / col_spacing).ceil
|
26
26
|
i_max = (radius / row_spacing).ceil
|
27
27
|
|
28
|
-
collection = GeoJSON::Collection.new(@map.projection)
|
28
|
+
collection = GeoJSON::Collection.new(projection: @map.neatline.projection)
|
29
29
|
(-j_max..j_max).each do |j|
|
30
30
|
x = j * col_spacing + col_offset
|
31
|
-
coordinates = [[x,
|
32
|
-
point.rotate_by_degrees(-
|
31
|
+
coordinates = [[x, radius], [x, -radius]].map do |point|
|
32
|
+
point.rotate_by_degrees(declination - @map.rotation).plus @map.dimensions.times(0.5)
|
33
33
|
end
|
34
34
|
collection.add_linestring coordinates
|
35
35
|
(-i_max..i_max).reject(&j.even? ? :even? : :odd?).map do |i|
|
36
|
-
[x, i * row_spacing].rotate_by_degrees(-
|
36
|
+
[x, i * row_spacing].rotate_by_degrees(declination - @map.rotation).plus @map.dimensions.times(0.5)
|
37
37
|
end.each do |coordinates|
|
38
38
|
collection.add_point coordinates, "rotation" => declination
|
39
39
|
end
|
@@ -45,7 +45,7 @@ module NSWTopo
|
|
45
45
|
lines = features.grep(GeoJSON::LineString)
|
46
46
|
return @name if lines.none?
|
47
47
|
line = lines.map(&:coordinates).max_by(&:distance)
|
48
|
-
angle = 90
|
48
|
+
angle = 90 + 180 * Math::atan2(*line.diff.reverse) / Math::PI + @map.rotation
|
49
49
|
"%s: %i line%s at %.1f°%s" % [@name, lines.length, (?s unless lines.one?), angle.abs, angle > 0 ? ?E : angle < 0 ? ?W : nil]
|
50
50
|
end
|
51
51
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module NSWTopo
|
2
2
|
module Feature
|
3
|
-
include Vector,
|
3
|
+
include Vector, Log
|
4
4
|
CREATE = %w[features]
|
5
5
|
|
6
6
|
def get_features
|
@@ -11,25 +11,34 @@ module NSWTopo
|
|
11
11
|
else raise "#{@source.basename}: invalid or no features specified"
|
12
12
|
end
|
13
13
|
end.slice_before do |args|
|
14
|
-
!args
|
14
|
+
!args.delete(:fallback)
|
15
15
|
end.map do |fallbacks|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
16
|
+
fallbacks.each.with_object({})
|
17
|
+
end.map do |fallbacks|
|
18
|
+
args, options = *fallbacks.next
|
19
|
+
source, error = args.delete(:source), nil
|
20
|
+
source = @path if @path
|
21
|
+
log_update "%s: %s" % [@name, options.any? ? "failed to retrieve features, trying fallback source" : "retrieving features"]
|
22
|
+
raise "#{@source.basename}: no feature source defined" unless source
|
23
|
+
source_path = Pathname(source).expand_path(@source.parent)
|
24
|
+
options.merge! args
|
25
|
+
collection = case
|
26
|
+
when ArcGIS::Service === source
|
27
|
+
layer = ArcGIS::Service.new(source).layer(**options.slice(:layer, :where), geometry: @map.neatline(**MARGIN).bbox, decode: true)
|
28
|
+
layer.features(**options.slice(:per_page)) do |count, total|
|
29
|
+
log_update "%s: retrieved %i of %i feature%s" % [@name, count, total, (?s if total > 1)]
|
30
|
+
end.reproject_to(@map.neatline.projection)
|
31
|
+
when Shapefile::Source === source_path
|
32
|
+
layer = Shapefile::Source.new(source_path).layer(**options.slice(:where, :sql, :layer), geometry: @map.neatline(**MARGIN), projection: @map.neatline.projection)
|
33
|
+
layer.features
|
34
|
+
else
|
26
35
|
raise "#{@source.basename}: invalid feature source: #{source}"
|
27
|
-
rescue ArcGISServer::Error => error
|
28
|
-
next options, nil, error
|
29
36
|
end
|
30
|
-
|
31
|
-
|
32
|
-
|
37
|
+
next collection, options
|
38
|
+
rescue ArcGIS::Connection::Error => error
|
39
|
+
retry
|
40
|
+
rescue StopIteration
|
41
|
+
raise error
|
33
42
|
end.each do |collection, options|
|
34
43
|
rotation_attribute, arithmetic = case options[:rotation]
|
35
44
|
when /^90 - (\w+)$/ then [$1, true]
|
@@ -37,15 +46,15 @@ module NSWTopo
|
|
37
46
|
end
|
38
47
|
|
39
48
|
collection.each do |feature|
|
40
|
-
categories = [*options[:category]].
|
49
|
+
categories = [*options[:category]].flat_map do |category|
|
41
50
|
Hash === category ? [*category] : [category]
|
42
|
-
end.
|
51
|
+
end.map do |attribute, substitutions|
|
43
52
|
value = feature.fetch(attribute, attribute)
|
44
53
|
substitutions ? substitutions.fetch(value, value) : value
|
45
54
|
end
|
46
55
|
|
47
56
|
options[:sizes].tap do |mm, max = 9|
|
48
|
-
unit =
|
57
|
+
unit = (mm == true ? 5 : mm)
|
49
58
|
case feature
|
50
59
|
when GeoJSON::LineString, GeoJSON::MultiLineString
|
51
60
|
size = (Math::log2(feature.length) - Math::log2(unit)).ceil rescue 0
|
@@ -71,17 +80,22 @@ module NSWTopo
|
|
71
80
|
feature.fetch(attribute, attribute)
|
72
81
|
end.map(&:to_s).reject(&:empty?)
|
73
82
|
|
83
|
+
dual = options[:dual].then do |attribute|
|
84
|
+
feature.fetch(attribute, attribute) if attribute
|
85
|
+
end
|
86
|
+
|
74
87
|
categories = categories.map(&:to_s).reject(&:empty?).map(&method(:categorise))
|
75
88
|
properties = {}
|
76
89
|
properties["category"] = categories if categories.any?
|
77
90
|
properties["label"] = labels if labels.any?
|
91
|
+
properties["dual"] = dual if dual
|
78
92
|
properties["draw"] = false if options[:draw] == false
|
79
|
-
properties["draw"] = false if @name =~
|
93
|
+
properties["draw"] = false if @name =~ /[-_]labels$/ && !options.key?(:draw)
|
80
94
|
properties["rotation"] = rotation if rotation
|
81
95
|
|
82
96
|
feature.properties.replace properties
|
83
97
|
end
|
84
|
-
end.map(&:first).inject(&:merge)
|
98
|
+
end.map(&:first).inject(&:merge).rename(@name)
|
85
99
|
end
|
86
100
|
end
|
87
101
|
end
|
data/lib/nswtopo/layer/grid.rb
CHANGED
@@ -1,21 +1,16 @@
|
|
1
1
|
module NSWTopo
|
2
2
|
module Grid
|
3
3
|
include Vector
|
4
|
-
CREATE = %w[interval]
|
4
|
+
CREATE = %w[interval border]
|
5
5
|
INSET = 1.5
|
6
6
|
DEFAULTS = YAML.load <<~YAML
|
7
7
|
interval: 1000.0
|
8
8
|
stroke: black
|
9
9
|
stroke-width: 0.1
|
10
|
-
|
11
|
-
|
10
|
+
edge:
|
11
|
+
fill: none
|
12
|
+
preserve: true
|
12
13
|
labels:
|
13
|
-
dupe: outline
|
14
|
-
outline:
|
15
|
-
stroke: white
|
16
|
-
fill: none
|
17
|
-
stroke-width: 10%
|
18
|
-
opacity: 0.65
|
19
14
|
font-family: Arial Narrow, sans-serif
|
20
15
|
font-size: 2.3
|
21
16
|
stroke: none
|
@@ -24,11 +19,11 @@ module NSWTopo
|
|
24
19
|
YAML
|
25
20
|
|
26
21
|
def get_features
|
27
|
-
Projection.utm_zones(@map.
|
28
|
-
utm,
|
29
|
-
|
22
|
+
Projection.utm_zones(@map.neatline).flat_map do |zone|
|
23
|
+
utm, utm_geometry = Projection.utm(zone), Projection.utm_geometry(zone)
|
24
|
+
map_geometry = @map.neatline(**MARGIN).reproject_to_wgs84
|
30
25
|
|
31
|
-
eastings, northings = @map.
|
26
|
+
eastings, northings = @map.neatline.reproject_to(utm).bounds.map do |min, max|
|
32
27
|
(min / @interval).floor..(max / @interval).ceil
|
33
28
|
end.map do |counts|
|
34
29
|
counts.map { |count| count * @interval }
|
@@ -39,27 +34,34 @@ module NSWTopo
|
|
39
34
|
end
|
40
35
|
|
41
36
|
eastings, northings = [grid, grid.transpose].map.with_index do |lines, index|
|
42
|
-
lines.inject GeoJSON::Collection.new(utm) do |collection, line|
|
37
|
+
lines.inject GeoJSON::Collection.new(projection: utm) do |collection, line|
|
43
38
|
coord = line[0][index]
|
44
39
|
label = [coord / 100000, (coord / 1000) % 100]
|
45
40
|
label << coord % 1000 unless @interval % 1000 == 0
|
46
41
|
collection.add_linestring line, "label" => label, "ends" => [0, 1], "category" => index.zero? ? "easting" : "northing"
|
47
|
-
end.reproject_to_wgs84.clip
|
42
|
+
end.reproject_to_wgs84.clip(utm_geometry).clip(map_geometry).explode.each do |linestring|
|
48
43
|
linestring["ends"].delete 0 if linestring.coordinates[0][0] % 6 < 0.00001
|
49
44
|
linestring["ends"].delete 1 if linestring.coordinates[-1][0] % 6 < 0.00001
|
50
45
|
end
|
51
46
|
end
|
52
47
|
|
53
|
-
boundary_points = GeoJSON.multilinestring(grid.transpose, projection: utm).reproject_to_wgs84.clip
|
48
|
+
boundary_points = GeoJSON.multilinestring(grid.transpose, projection: utm).reproject_to_wgs84.clip(utm_geometry).coordinates.map(&:first)
|
54
49
|
boundary = GeoJSON.linestring boundary_points, properties: { "category" => "boundary" }
|
55
50
|
|
56
51
|
[eastings, northings, boundary]
|
57
|
-
end.
|
52
|
+
end.tap do |collections|
|
53
|
+
next unless @border
|
54
|
+
mm = -0.5 * @params["stroke-width"]
|
55
|
+
@map.neatline(mm: mm).reproject_to_wgs84.tap do |border|
|
56
|
+
border.properties.replace "category" => "edge"
|
57
|
+
collections << border
|
58
|
+
end
|
59
|
+
end.inject(&:merge)
|
58
60
|
end
|
59
61
|
|
60
62
|
def label_element(labels, label_params)
|
61
63
|
font_size = label_params["font-size"]
|
62
|
-
parts = labels.zip(
|
64
|
+
parts = labels.zip(["%d\u00a0", "%02d", "\u00a0%03d"]).map do |part, format|
|
63
65
|
format % part
|
64
66
|
end.zip([80, 100, 80])
|
65
67
|
|
@@ -70,19 +72,20 @@ module NSWTopo
|
|
70
72
|
tspan.add_text text
|
71
73
|
end
|
72
74
|
|
73
|
-
text_length = parts.
|
74
|
-
|
75
|
-
end
|
75
|
+
text_length = parts.sum do |text, percent|
|
76
|
+
Font.glyph_length text, label_params.merge("font-size" => font_size * percent / 100.0)
|
77
|
+
end
|
76
78
|
text_path.add_attribute "textLength", VALUE % text_length
|
77
79
|
[text_length, text_path]
|
78
80
|
end
|
79
81
|
|
80
82
|
def labeling_features
|
83
|
+
return [] if @params["unlabeled"]
|
81
84
|
label_params = @params["labels"]
|
82
85
|
font_size = label_params["font-size"]
|
83
|
-
offset = 0.85 * font_size
|
86
|
+
offset = -0.85 * font_size
|
84
87
|
inset = INSET + font_size * 0.5 * Math::sin(@map.rotation.abs * Math::PI / 180)
|
85
|
-
|
88
|
+
inset_geometry = @map.neatline(mm: -inset)
|
86
89
|
|
87
90
|
gridlines = features.select do |linestring|
|
88
91
|
linestring["label"]
|
@@ -99,14 +102,14 @@ module NSWTopo
|
|
99
102
|
easting["ends"].map! { |index| 1 - index }
|
100
103
|
end if flip_eastings
|
101
104
|
|
102
|
-
gridlines.map do |gridline|
|
103
|
-
gridline.offset(offset, splits: false)
|
104
|
-
end.
|
105
|
+
gridlines.inject(GeoJSON::Collection.new(projection: @map.neatline.projection)) do |collection, gridline|
|
106
|
+
collection << gridline.offset(offset, splits: false)
|
107
|
+
end.clip(inset_geometry).explode.flat_map do |gridline|
|
105
108
|
label, ends = gridline.values_at "label", "ends"
|
106
109
|
%i[itself reverse].values_at(*ends).map do |order|
|
107
110
|
text_length, text_path = label_element(label, label_params)
|
108
111
|
segment = gridline.coordinates.send(order).take(2)
|
109
|
-
fraction = text_length
|
112
|
+
fraction = text_length / segment.distance
|
110
113
|
coordinates = [segment[0], segment.along(fraction)].send(order)
|
111
114
|
GeoJSON::LineString.new coordinates, "label" => text_path
|
112
115
|
end
|
data/lib/nswtopo/layer/import.rb
CHANGED
@@ -1,25 +1,5 @@
|
|
1
1
|
module NSWTopo
|
2
2
|
module Import
|
3
|
-
include Raster
|
4
|
-
|
5
|
-
def get_raster(temp_dir)
|
6
|
-
crop_path = temp_dir / "crop.tif"
|
7
|
-
|
8
|
-
json = begin
|
9
|
-
OS.gdalinfo "-json", @path
|
10
|
-
rescue OS::Error
|
11
|
-
raise "invalid raster file: #{@path}"
|
12
|
-
end
|
13
|
-
palette = JSON.parse(json)["bands"].any? do |band|
|
14
|
-
"Palette" == band["colorInterpretation"]
|
15
|
-
end
|
16
|
-
|
17
|
-
projection = Projection.new(@path)
|
18
|
-
args = ["-projwin", *@map.projwin(projection), @path, crop_path]
|
19
|
-
args = ["-expand", "rgba", *args] if palette
|
20
|
-
OS.gdal_translate *args
|
21
|
-
|
22
|
-
return Numeric === @resolution ? @resolution : @map.get_raster_resolution(crop_path), crop_path
|
23
|
-
end
|
3
|
+
include RasterImport, Raster, RasterRender
|
24
4
|
end
|
25
5
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module NSWTopo
|
2
|
+
module Labels
|
3
|
+
class Barrier
|
4
|
+
class Segment
|
5
|
+
def initialize(segment, barrier)
|
6
|
+
@segment, @barrier = segment, barrier
|
7
|
+
@bounds = @segment.transpose.map(&:minmax).map do |min, max|
|
8
|
+
[min - barrier.buffer, max + barrier.buffer]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
attr_reader :barrier, :bounds
|
12
|
+
|
13
|
+
def conflicts_with?(segment, buffer: 0)
|
14
|
+
[@segment, segment].overlap?(@barrier.buffer + buffer)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(feature, buffer)
|
19
|
+
@feature, @buffer = feature, buffer
|
20
|
+
end
|
21
|
+
attr_reader :buffer
|
22
|
+
|
23
|
+
def segments
|
24
|
+
case @feature
|
25
|
+
when GeoJSON::Point
|
26
|
+
[[@feature.coordinates] * 2]
|
27
|
+
when GeoJSON::LineString
|
28
|
+
@feature.coordinates.segments
|
29
|
+
when GeoJSON::Polygon
|
30
|
+
@feature.coordinates.flat_map do |coordinates|
|
31
|
+
coordinates.segments
|
32
|
+
end
|
33
|
+
end.map do |segment|
|
34
|
+
Segment.new segment, self
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|