nswtopo 3.0.1 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/nswtopo +20 -4
- data/docs/contours.md +2 -0
- data/docs/relief.md +2 -3
- data/docs/spot-heights.md +2 -0
- data/lib/nswtopo/archive.rb +6 -3
- data/lib/nswtopo/chrome.rb +9 -6
- data/lib/nswtopo/commands/layers.rb +2 -2
- data/lib/nswtopo/config.rb +1 -0
- data/lib/nswtopo/formats/gemf.rb +1 -0
- data/lib/nswtopo/formats/kmz.rb +16 -10
- data/lib/nswtopo/formats/mbtiles.rb +1 -0
- data/lib/nswtopo/formats/pdf.rb +4 -3
- data/lib/nswtopo/formats/svg.rb +5 -13
- data/lib/nswtopo/formats/svgz.rb +1 -0
- data/lib/nswtopo/formats/zip.rb +5 -4
- data/lib/nswtopo/formats.rb +35 -36
- data/lib/nswtopo/geometry/r_tree.rb +24 -23
- data/lib/nswtopo/geometry/straight_skeleton/node.rb +4 -4
- data/lib/nswtopo/geometry/straight_skeleton/nodes.rb +51 -40
- data/lib/nswtopo/geometry/straight_skeleton/split.rb +2 -2
- data/lib/nswtopo/geometry/vector.rb +55 -49
- data/lib/nswtopo/geometry.rb +0 -5
- data/lib/nswtopo/gis/arcgis/layer/map.rb +11 -10
- data/lib/nswtopo/gis/arcgis/layer/query.rb +8 -10
- data/lib/nswtopo/gis/arcgis/layer.rb +7 -11
- data/lib/nswtopo/gis/dem.rb +3 -2
- data/lib/nswtopo/gis/gdal_glob.rb +3 -3
- data/lib/nswtopo/gis/geojson/collection.rb +60 -14
- data/lib/nswtopo/gis/geojson/line_string.rb +142 -1
- data/lib/nswtopo/gis/geojson/multi_line_string.rb +49 -7
- data/lib/nswtopo/gis/geojson/multi_point.rb +87 -0
- data/lib/nswtopo/gis/geojson/multi_polygon.rb +35 -23
- data/lib/nswtopo/gis/geojson/point.rb +16 -1
- data/lib/nswtopo/gis/geojson/polygon.rb +69 -7
- data/lib/nswtopo/gis/geojson.rb +92 -46
- data/lib/nswtopo/gis/projection.rb +5 -1
- data/lib/nswtopo/helpers/thread_pool.rb +39 -0
- data/lib/nswtopo/helpers.rb +44 -5
- data/lib/nswtopo/layer/arcgis_raster.rb +4 -6
- data/lib/nswtopo/layer/contour.rb +24 -26
- data/lib/nswtopo/layer/control.rb +5 -3
- data/lib/nswtopo/layer/declination.rb +14 -10
- data/lib/nswtopo/layer/feature.rb +5 -5
- data/lib/nswtopo/layer/grid.rb +19 -18
- data/lib/nswtopo/layer/labels/barriers.rb +23 -0
- data/lib/nswtopo/layer/labels/convex_hull.rb +12 -0
- data/lib/nswtopo/layer/labels/convex_hulls.rb +86 -0
- data/lib/nswtopo/layer/labels/label.rb +63 -0
- data/lib/nswtopo/layer/labels.rb +192 -315
- data/lib/nswtopo/layer/overlay.rb +11 -12
- data/lib/nswtopo/layer/raster.rb +1 -0
- data/lib/nswtopo/layer/relief.rb +6 -4
- data/lib/nswtopo/layer/spot.rb +11 -17
- data/lib/nswtopo/layer/{vector → vector_render}/cutout.rb +1 -1
- data/lib/nswtopo/layer/{vector → vector_render}/knockout.rb +2 -3
- data/lib/nswtopo/layer/{vector.rb → vector_render.rb} +20 -45
- data/lib/nswtopo/layer.rb +2 -1
- data/lib/nswtopo/map.rb +70 -56
- data/lib/nswtopo/svg.rb +5 -0
- data/lib/nswtopo/tiled_web_map.rb +3 -3
- data/lib/nswtopo/tree_indenter.rb +2 -2
- data/lib/nswtopo/version.rb +1 -1
- data/lib/nswtopo.rb +4 -0
- metadata +15 -17
- data/lib/nswtopo/geometry/overlap.rb +0 -47
- data/lib/nswtopo/geometry/segment.rb +0 -27
- data/lib/nswtopo/geometry/vector_sequence.rb +0 -180
- data/lib/nswtopo/helpers/array.rb +0 -19
- data/lib/nswtopo/helpers/concurrently.rb +0 -27
- data/lib/nswtopo/helpers/dir.rb +0 -7
- data/lib/nswtopo/helpers/hash.rb +0 -15
- data/lib/nswtopo/helpers/tar_writer.rb +0 -11
- data/lib/nswtopo/layer/labels/barrier.rb +0 -39
data/lib/nswtopo/gis/geojson.rb
CHANGED
@@ -7,36 +7,50 @@ module NSWTopo
|
|
7
7
|
|
8
8
|
CLASSES = TYPES.map do |type|
|
9
9
|
klass = Class.new do
|
10
|
-
def initialize(coordinates, properties =
|
11
|
-
properties
|
12
|
-
raise Error, "invalid feature properties" unless Hash === properties
|
13
|
-
|
14
|
-
@coordinates
|
15
|
-
|
16
|
-
|
17
|
-
attr_accessor :coordinates, :properties
|
18
|
-
|
19
|
-
define_method :to_h do
|
20
|
-
{
|
21
|
-
"type" => "Feature",
|
22
|
-
"geometry" => {
|
23
|
-
"type" => type,
|
24
|
-
"coordinates" => @coordinates
|
25
|
-
},
|
26
|
-
"properties" => @properties
|
27
|
-
}
|
10
|
+
def initialize(coordinates, properties = nil, &block)
|
11
|
+
@coordinates, @properties = coordinates, properties || {}
|
12
|
+
raise Error, "invalid feature properties" unless Hash === @properties
|
13
|
+
instance_eval(&block) if block_given?
|
14
|
+
@coordinates.freeze
|
15
|
+
@properties.freeze
|
16
|
+
freeze!
|
28
17
|
end
|
29
18
|
|
19
|
+
alias freeze! freeze
|
20
|
+
|
21
|
+
attr_reader :coordinates, :properties
|
22
|
+
|
30
23
|
extend Forwardable
|
31
|
-
delegate %i[[]
|
24
|
+
delegate %i[[] fetch values_at key? slice except dig] => :@properties
|
25
|
+
delegate %i[empty?] => :@coordinates
|
26
|
+
delegate %i[to_json] => :to_h
|
27
|
+
end
|
28
|
+
|
29
|
+
klass.define_method :to_h do
|
30
|
+
{
|
31
|
+
"type" => "Feature",
|
32
|
+
"geometry" => {
|
33
|
+
"type" => type,
|
34
|
+
"coordinates" => @coordinates
|
35
|
+
},
|
36
|
+
"properties" => @properties
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
klass.define_method :with_properties do |properties|
|
41
|
+
klass.new @coordinates, **properties
|
32
42
|
end
|
33
43
|
|
34
|
-
|
44
|
+
klass.define_method :add_properties do |properties|
|
45
|
+
klass.new @coordinates, **@properties, **properties
|
46
|
+
end
|
47
|
+
|
48
|
+
next type, const_set(type, klass)
|
35
49
|
end
|
36
50
|
|
37
|
-
CLASSES.
|
38
|
-
Collection.define_method "add_#{type}".downcase do |coordinates, properties =
|
39
|
-
self << klass
|
51
|
+
CLASSES.each do |type, klass|
|
52
|
+
Collection.define_method "add_#{type}".downcase do |coordinates, properties = nil|
|
53
|
+
self << klass[coordinates, properties]
|
40
54
|
end
|
41
55
|
|
42
56
|
Collection.define_method "#{type}s".downcase do
|
@@ -47,45 +61,76 @@ module NSWTopo
|
|
47
61
|
one? && klass === first
|
48
62
|
end
|
49
63
|
|
50
|
-
define_singleton_method type.downcase do |coordinates, projection: DEFAULT_PROJECTION, name: nil, properties:
|
51
|
-
Collection.new(projection: projection, name: name) << klass
|
64
|
+
define_singleton_method type.downcase do |coordinates, projection: DEFAULT_PROJECTION, name: nil, properties: nil|
|
65
|
+
Collection.new(projection: projection, name: name) << klass[coordinates, properties]
|
52
66
|
end
|
53
67
|
end
|
54
68
|
|
55
|
-
[[Point, MultiPoint ],
|
56
|
-
|
57
|
-
|
69
|
+
[ [Point, MultiPoint ],
|
70
|
+
[LineString, MultiLineString],
|
71
|
+
[Polygon, MultiPolygon ]
|
72
|
+
].each do |single_class, multi_class|
|
58
73
|
single_class.class_eval do
|
59
|
-
|
60
|
-
|
61
|
-
|
74
|
+
include Enumerable
|
75
|
+
delegate %i[each] => :@coordinates
|
76
|
+
delegate %i[clip dissolve_points +] => :multi
|
62
77
|
|
63
|
-
|
64
|
-
|
65
|
-
end
|
78
|
+
def explode = [self]
|
79
|
+
end
|
66
80
|
|
67
|
-
|
68
|
-
|
81
|
+
single_class.define_method :multi do
|
82
|
+
multi_class.new [@coordinates], @properties
|
69
83
|
end
|
70
84
|
|
71
85
|
multi_class.class_eval do
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
86
|
+
include Enumerable
|
87
|
+
|
88
|
+
alias explode entries
|
89
|
+
alias multi itself
|
90
|
+
|
91
|
+
def bounds
|
92
|
+
map(&:bounds).transpose.map(&:flatten).map(&:minmax)
|
76
93
|
end
|
77
94
|
|
78
|
-
def
|
79
|
-
|
95
|
+
def empty_points = MultiPoint.new([], @properties)
|
96
|
+
def empty_linestrings = MultiLineString.new([], @properties)
|
97
|
+
def empty_polygons = MultiPolygon.new([], @properties)
|
98
|
+
end
|
99
|
+
|
100
|
+
multi_class.define_singleton_method :[] do |coordinates, properties = nil, &block|
|
101
|
+
multi_class.new(coordinates, properties) do
|
102
|
+
@coordinates.each do |coordinates|
|
103
|
+
single_class[coordinates]
|
104
|
+
end
|
105
|
+
block&.call self
|
80
106
|
end
|
107
|
+
end
|
81
108
|
|
82
|
-
|
83
|
-
|
109
|
+
multi_class.define_method :each do |&block|
|
110
|
+
enum = Enumerator.new do |yielder|
|
111
|
+
@coordinates.each do |coordinates|
|
112
|
+
yielder << single_class.new(coordinates, @properties)
|
113
|
+
end
|
84
114
|
end
|
115
|
+
block ? enum.each(&block) : enum
|
116
|
+
end
|
85
117
|
|
86
|
-
|
118
|
+
multi_class.define_method :+ do |other|
|
119
|
+
case other
|
120
|
+
when single_class
|
121
|
+
multi_class.new @coordinates + [other.coordinates], @properties
|
122
|
+
when multi_class
|
123
|
+
multi_class.new @coordinates + other.coordinates, @properties
|
124
|
+
else
|
125
|
+
raise "heterogenous geometries not implemented"
|
126
|
+
end
|
127
|
+
end
|
87
128
|
|
88
|
-
|
129
|
+
single_type, _ = CLASSES.rassoc(single_class)
|
130
|
+
%i[select reject].each do |verb|
|
131
|
+
multi_class.define_method "#{verb}_#{single_type}s".downcase do |&block|
|
132
|
+
send(verb, &block).inject(multi_class.new([], @properties), &:+)
|
133
|
+
end
|
89
134
|
end
|
90
135
|
end
|
91
136
|
end
|
@@ -94,5 +139,6 @@ end
|
|
94
139
|
require_relative 'geojson/point'
|
95
140
|
require_relative 'geojson/line_string'
|
96
141
|
require_relative 'geojson/polygon'
|
142
|
+
require_relative 'geojson/multi_point'
|
97
143
|
require_relative 'geojson/multi_line_string'
|
98
144
|
require_relative 'geojson/multi_polygon'
|
@@ -10,7 +10,7 @@ module NSWTopo
|
|
10
10
|
alias to_str wkt2
|
11
11
|
|
12
12
|
def ==(other)
|
13
|
-
wkt2 == other.wkt2
|
13
|
+
super || wkt2 == other.wkt2
|
14
14
|
end
|
15
15
|
|
16
16
|
extend Forwardable
|
@@ -29,6 +29,10 @@ module NSWTopo
|
|
29
29
|
new("EPSG:4326")
|
30
30
|
end
|
31
31
|
|
32
|
+
def self.epsg(epsg)
|
33
|
+
new("EPSG:#{epsg}")
|
34
|
+
end
|
35
|
+
|
32
36
|
def self.from(**params)
|
33
37
|
params.map do |key, value|
|
34
38
|
"+#{key}=#{value}"
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class ThreadPool
|
2
|
+
CORES = Etc.nprocessors rescue 1
|
3
|
+
|
4
|
+
def initialize(size = CORES)
|
5
|
+
@args, @size = [], size
|
6
|
+
end
|
7
|
+
|
8
|
+
def <<(args)
|
9
|
+
tap { @args << args }
|
10
|
+
end
|
11
|
+
|
12
|
+
def threads(queue, &block)
|
13
|
+
@size.times.map do
|
14
|
+
Thread.new do
|
15
|
+
while args = queue.pop
|
16
|
+
block.call(*args)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def each(&block)
|
23
|
+
queue = Queue.new
|
24
|
+
threads(queue, &block).tap do
|
25
|
+
@args.inject(queue, &:<<).close
|
26
|
+
end.each(&:join)
|
27
|
+
@args
|
28
|
+
end
|
29
|
+
|
30
|
+
def in_groups(&block)
|
31
|
+
queue = Queue.new
|
32
|
+
threads(queue, &block).tap do
|
33
|
+
@args.group_by.with_index do |args, index|
|
34
|
+
index % @size
|
35
|
+
end.values.inject(queue, &:<<).close
|
36
|
+
end.each(&:join)
|
37
|
+
@args
|
38
|
+
end
|
39
|
+
end
|
data/lib/nswtopo/helpers.rb
CHANGED
@@ -1,6 +1,45 @@
|
|
1
|
-
require_relative 'helpers/
|
2
|
-
require_relative 'helpers/array'
|
3
|
-
require_relative 'helpers/dir'
|
4
|
-
require_relative 'helpers/concurrently'
|
5
|
-
require_relative 'helpers/tar_writer'
|
1
|
+
require_relative 'helpers/thread_pool'
|
6
2
|
require_relative 'helpers/colour'
|
3
|
+
|
4
|
+
module Helpers
|
5
|
+
refine Dir.singleton_class do
|
6
|
+
def mktmppath
|
7
|
+
mktmpdir("nswtopo_") do |path|
|
8
|
+
yield Pathname.new(path)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
refine Hash do
|
14
|
+
def deep_merge(other)
|
15
|
+
merge(other) do |key, old_value, new_value|
|
16
|
+
Hash === old_value && Hash === new_value ? old_value.deep_merge(new_value) : new_value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
refine Array do
|
22
|
+
# partially partition element range in-place, according to block, returning the partitioned ranges
|
23
|
+
def partition!(range, &block)
|
24
|
+
return range.begin...range.begin, range if range.one?
|
25
|
+
last, pivot = range.end - 1, Kernel.rand(range)
|
26
|
+
self[pivot], self[last] = self[last], self[pivot]
|
27
|
+
pivot_value = block.call(at last)
|
28
|
+
index = range.inject(range.begin) do |store, index|
|
29
|
+
next store unless index == last || block.call(at index) < pivot_value
|
30
|
+
self[index], self[store] = self[store], self[index]
|
31
|
+
store + 1
|
32
|
+
end
|
33
|
+
return range.begin...index, index...range.end
|
34
|
+
end
|
35
|
+
|
36
|
+
def median_partition!(range = 0...length, &block)
|
37
|
+
median, target = (range.begin + range.end) / 2, range
|
38
|
+
while target.begin != median
|
39
|
+
lower, upper = partition!(target, &block)
|
40
|
+
target = lower === median ? lower : upper
|
41
|
+
end
|
42
|
+
return range.begin...median, median...range.end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -26,17 +26,15 @@ module NSWTopo
|
|
26
26
|
end || lods.last
|
27
27
|
tile_level, tile_resolution = lod.values_at "level", "resolution"
|
28
28
|
|
29
|
-
target_bbox.
|
30
|
-
|
31
|
-
end.transpose.map(&:minmax).zip(tile_sizes).map do |bound, tile_size|
|
32
|
-
bound / tile_resolution / tile_size
|
29
|
+
target_bbox.bounds.zip(origin, tile_sizes).map do |(min, max), origin, tile_size|
|
30
|
+
[(min - origin) / tile_resolution / tile_size, (max - origin) / tile_resolution / tile_size]
|
33
31
|
end.map do |min, max|
|
34
32
|
(min.floor..max.ceil).each_cons(2).to_a
|
35
33
|
end.inject(&:product).inject(GeoJSON::Collection.new(projection: service.projection)) do |tiles, (cols, rows)|
|
36
34
|
[cols, rows].zip(tile_sizes).map do |indices, tile_size|
|
37
|
-
indices.
|
35
|
+
indices.map { |index| index * tile_size * tile_resolution }
|
38
36
|
end.transpose.map do |corner|
|
39
|
-
corner.
|
37
|
+
corner.zip(origin).map(&:sum)
|
40
38
|
end.transpose.then do |bounds|
|
41
39
|
ring = bounds.inject(&:product).values_at(0,2,3,1,0)
|
42
40
|
ullr = bounds.inject(&:product).values_at(1,2).flatten
|
@@ -1,7 +1,9 @@
|
|
1
1
|
module NSWTopo
|
2
2
|
module Contour
|
3
|
-
|
4
|
-
|
3
|
+
using Helpers
|
4
|
+
include VectorRender, DEM, Log
|
5
|
+
|
6
|
+
CREATE = %w[interval index auxiliary smooth simplify thin density min-length no-depression knolls fill epsg]
|
5
7
|
DEFAULTS = YAML.load <<~YAML
|
6
8
|
interval: 5
|
7
9
|
smooth: 0.2
|
@@ -36,7 +38,7 @@ module NSWTopo
|
|
36
38
|
YAML
|
37
39
|
|
38
40
|
def margin
|
39
|
-
{ mm: [3 * @smooth, 1].
|
41
|
+
{ mm: [3 * @smooth, 1].max }
|
40
42
|
end
|
41
43
|
|
42
44
|
def check_geos!
|
@@ -54,7 +56,7 @@ module NSWTopo
|
|
54
56
|
@index ||= 10 * @interval
|
55
57
|
@params = {
|
56
58
|
"Index" => { "stroke-width" => 2 * @params["stroke-width"] },
|
57
|
-
"labels" => { "fill" => @fill ||
|
59
|
+
"labels" => { "fill" => @fill || "black" }
|
58
60
|
}.deep_merge(@params)
|
59
61
|
|
60
62
|
check_geos! if @thin
|
@@ -75,14 +77,16 @@ module NSWTopo
|
|
75
77
|
|
76
78
|
log_update "%s: generating contour lines" % @name
|
77
79
|
json = OS.gdal_contour "-q", "-a", "elevation", "-i", @interval, "-f", "GeoJSON", "-lco", "RFC7946=NO", blur_path, "/vsistdout/"
|
78
|
-
contours = GeoJSON::Collection.load
|
80
|
+
contours = GeoJSON::Collection.load(json, projection: @map.projection).map! do |feature|
|
81
|
+
id, elevation = feature.values_at "ID", "elevation"
|
82
|
+
properties = { "id" => id, "elevation" => elevation, "modulo" => elevation % @index, "depression" => feature.closed? && feature.anticlockwise? ? 1 : 0}
|
83
|
+
feature.with_properties(properties)
|
84
|
+
end
|
85
|
+
raise "no elevation data found in map area" unless contours.any?
|
79
86
|
|
80
87
|
if @no_depression.nil?
|
81
88
|
candidates = contours.select do |feature|
|
82
|
-
feature
|
83
|
-
feature.coordinates.anticlockwise?
|
84
|
-
end.each do |feature|
|
85
|
-
feature["depression"] = 1
|
89
|
+
feature["depression"] == 1
|
86
90
|
end
|
87
91
|
index = RTree.load(candidates, &:bounds)
|
88
92
|
|
@@ -90,29 +94,21 @@ module NSWTopo
|
|
90
94
|
next unless feature["depression"] == 1
|
91
95
|
index.search(feature.bounds).none? do |other|
|
92
96
|
next if other == feature
|
93
|
-
feature.
|
94
|
-
other.coordinates.first.within?(feature.coordinates)
|
97
|
+
feature.to_polygon.contains?(other.first) || other.to_polygon.contains?(feature.first)
|
95
98
|
end
|
96
99
|
end
|
97
100
|
end
|
98
101
|
|
99
102
|
contours.reject! do |feature|
|
100
|
-
feature.
|
103
|
+
feature.closed? &&
|
101
104
|
feature.bounds.all? { |min, max| max - min < @knolls }
|
102
|
-
end
|
103
|
-
|
104
|
-
contours.each do |feature|
|
105
|
-
id, elevation, depression = feature.values_at "ID", "elevation", "depression"
|
106
|
-
feature.properties.replace("id" => id, "elevation" => elevation, "modulo" => elevation % @index, "depression" => depression || 0)
|
107
|
-
end
|
108
|
-
|
109
|
-
contours.reject! do |feature|
|
105
|
+
end.reject! do |feature|
|
110
106
|
feature["elevation"].zero?
|
111
107
|
end
|
112
108
|
|
113
109
|
contours.each_slice(100).inject(nil) do |update, features|
|
114
110
|
OS.ogr2ogr "-a_srs", @map.projection, "-nln", "contour", *update, "-simplify", @simplify, *db_flags, db_path, "GeoJSON:/vsistdin/" do |stdin|
|
115
|
-
stdin.write
|
111
|
+
stdin.write contours.with_features(features).to_json
|
116
112
|
end
|
117
113
|
%w[-update -append]
|
118
114
|
end
|
@@ -208,7 +204,7 @@ module NSWTopo
|
|
208
204
|
end
|
209
205
|
|
210
206
|
json = OS.ogr2ogr "-f", "GeoJSON", "-lco", "RFC7946=NO", "/vsistdout/", db_path, "contour"
|
211
|
-
GeoJSON::Collection.load(json, projection: @map.projection).
|
207
|
+
GeoJSON::Collection.load(json, projection: @map.projection).map! do |feature|
|
212
208
|
elevation, modulo, depression = feature.values_at "elevation", "modulo", "depression"
|
213
209
|
category = case
|
214
210
|
when @auxiliary && elevation % (2 * @interval) != 0 then %w[Auxiliary]
|
@@ -216,10 +212,12 @@ module NSWTopo
|
|
216
212
|
else %w[Standard]
|
217
213
|
end
|
218
214
|
category << "Depression" if depression == 1
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
215
|
+
|
216
|
+
properties = Hash[]
|
217
|
+
properties["elevation"] = elevation
|
218
|
+
properties["category"] = category
|
219
|
+
properties["label"] = elevation.to_i.to_s if modulo.zero?
|
220
|
+
feature.with_properties(properties)
|
223
221
|
end
|
224
222
|
end
|
225
223
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module NSWTopo
|
2
2
|
module Control
|
3
|
-
|
3
|
+
using Helpers
|
4
|
+
include VectorRender
|
5
|
+
|
4
6
|
CREATE = %w[diameter spot font-size colour]
|
5
7
|
DEFAULTS = YAML.load <<~YAML
|
6
8
|
diameter: 7.0
|
@@ -73,7 +75,7 @@ module NSWTopo
|
|
73
75
|
end
|
74
76
|
|
75
77
|
def to_s
|
76
|
-
counts = %w[control waterdrop hashhouse].
|
78
|
+
counts = %w[control waterdrop hashhouse].filter_map do |category|
|
77
79
|
waypoints = features.select do |feature|
|
78
80
|
feature["category"].any? category
|
79
81
|
end
|
@@ -82,7 +84,7 @@ module NSWTopo
|
|
82
84
|
next count unless "control" == category
|
83
85
|
total = features.sum { |feature| feature["label"].to_i.floor(-1) }
|
84
86
|
count << " (%i points)" % total
|
85
|
-
end
|
87
|
+
end
|
86
88
|
[@name, counts.join(", ")].join(": ")
|
87
89
|
end
|
88
90
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module NSWTopo
|
2
2
|
module Declination
|
3
|
-
include
|
3
|
+
include VectorRender
|
4
4
|
CREATE = %w[angle spacing arrows offset]
|
5
5
|
DEFAULTS = YAML.load <<~YAML
|
6
6
|
spacing: 40.0
|
@@ -21,31 +21,35 @@ module NSWTopo
|
|
21
21
|
row_spacing = @arrows * 0.5
|
22
22
|
col_offset = @offset % @spacing
|
23
23
|
|
24
|
-
radius = 0.5 * @map.neatline.bounds.transpose.
|
24
|
+
radius = 0.5 * @map.neatline.bounds.transpose.then do |bl, tr|
|
25
|
+
Vector[*tr] - Vector[*bl]
|
26
|
+
end.norm
|
27
|
+
|
25
28
|
j_max = (radius / col_spacing).ceil
|
26
29
|
i_max = (radius / row_spacing).ceil
|
27
30
|
|
28
|
-
|
29
|
-
(-j_max..j_max).each do |j|
|
31
|
+
(-j_max..j_max).each.with_object(GeoJSON::Collection.new(projection: @map.neatline.projection)) do |j, collection|
|
30
32
|
x = j * col_spacing + col_offset
|
31
|
-
coordinates = [
|
32
|
-
|
33
|
+
coordinates = [radius, -radius].map do |y|
|
34
|
+
Vector[x, y].rotate_by_degrees(declination - @map.rotation) + Vector[*@map.dimensions] / 2
|
33
35
|
end
|
34
36
|
collection.add_linestring coordinates
|
35
37
|
(-i_max..i_max).reject(&j.even? ? :even? : :odd?).map do |i|
|
36
|
-
[x, i * row_spacing].rotate_by_degrees(declination - @map.rotation)
|
38
|
+
Vector[x, i * row_spacing].rotate_by_degrees(declination - @map.rotation) + Vector[*@map.dimensions] / 2
|
37
39
|
end.each do |coordinates|
|
38
40
|
collection.add_point coordinates, "rotation" => declination
|
39
41
|
end
|
40
42
|
end
|
41
|
-
collection
|
42
43
|
end
|
43
44
|
|
44
45
|
def to_s
|
45
46
|
lines = features.grep(GeoJSON::LineString)
|
46
47
|
return @name if lines.none?
|
47
|
-
|
48
|
-
|
48
|
+
angle = lines.map(&:coordinates).map do |p0, p1|
|
49
|
+
p1 - p0
|
50
|
+
end.max_by(&:norm).then do |delta|
|
51
|
+
90 + 180 * Math::atan2(delta.y, delta.x) / Math::PI + @map.rotation
|
52
|
+
end
|
49
53
|
"%s: %i line%s at %.1f°%s" % [@name, lines.length, (?s unless lines.one?), angle.abs, angle > 0 ? ?E : angle < 0 ? ?W : nil]
|
50
54
|
end
|
51
55
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module NSWTopo
|
2
2
|
module Feature
|
3
|
-
include
|
3
|
+
include VectorRender, Log
|
4
4
|
CREATE = %w[features]
|
5
5
|
|
6
6
|
def get_features
|
@@ -45,7 +45,7 @@ module NSWTopo
|
|
45
45
|
when String then options[:rotation]
|
46
46
|
end
|
47
47
|
|
48
|
-
collection.
|
48
|
+
collection.map! do |feature|
|
49
49
|
categories = [*options[:category]].flat_map do |category|
|
50
50
|
Hash === category ? [*category] : [category]
|
51
51
|
end.map do |attribute, substitutions|
|
@@ -85,7 +85,7 @@ module NSWTopo
|
|
85
85
|
end
|
86
86
|
|
87
87
|
categories = categories.map(&:to_s).reject(&:empty?).map(&method(:categorise))
|
88
|
-
properties =
|
88
|
+
properties = Hash[]
|
89
89
|
properties["category"] = categories if categories.any?
|
90
90
|
properties["label"] = labels if labels.any?
|
91
91
|
properties["dual"] = dual if dual
|
@@ -93,9 +93,9 @@ module NSWTopo
|
|
93
93
|
properties["draw"] = false if @name =~ /[-_]labels$/ && !options.key?(:draw)
|
94
94
|
properties["rotation"] = rotation if rotation
|
95
95
|
|
96
|
-
feature.properties
|
96
|
+
feature.with_properties(properties)
|
97
97
|
end
|
98
|
-
end.map(&:first).inject(&:merge).
|
98
|
+
end.map(&:first).inject(&:merge).with_name(@name)
|
99
99
|
end
|
100
100
|
end
|
101
101
|
end
|
data/lib/nswtopo/layer/grid.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module NSWTopo
|
2
2
|
module Grid
|
3
|
-
include
|
3
|
+
include VectorRender
|
4
4
|
CREATE = %w[interval border]
|
5
5
|
INSET = 1.5
|
6
6
|
DEFAULTS = YAML.load <<~YAML
|
@@ -19,6 +19,7 @@ module NSWTopo
|
|
19
19
|
YAML
|
20
20
|
|
21
21
|
def get_features
|
22
|
+
@params["edge"]["stroke-width"] ||= 2 * @params["stroke-width"]
|
22
23
|
Projection.utm_zones(@map.neatline).flat_map do |zone|
|
23
24
|
utm, utm_geometry = Projection.utm(zone), Projection.utm_geometry(zone)
|
24
25
|
map_geometry = @map.neatline(**MARGIN).reproject_to_wgs84
|
@@ -40,8 +41,8 @@ module NSWTopo
|
|
40
41
|
label << coord % 1000 unless @interval % 1000 == 0
|
41
42
|
collection.add_linestring line, "label" => label, "ends" => [0, 1], "category" => index.zero? ? "easting" : "northing"
|
42
43
|
end.reproject_to_wgs84.clip(utm_geometry).clip(map_geometry).explode.each do |linestring|
|
43
|
-
linestring["ends"].delete 0 if linestring.coordinates
|
44
|
-
linestring["ends"].delete 1 if linestring.coordinates
|
44
|
+
linestring["ends"].delete 0 if linestring.coordinates.first.x % 6 < 0.00001
|
45
|
+
linestring["ends"].delete 1 if linestring.coordinates.last.x % 6 < 0.00001
|
45
46
|
end
|
46
47
|
end
|
47
48
|
|
@@ -51,9 +52,9 @@ module NSWTopo
|
|
51
52
|
[eastings, northings, boundary]
|
52
53
|
end.tap do |collections|
|
53
54
|
next unless @border
|
54
|
-
|
55
|
-
|
56
|
-
|
55
|
+
@map.neatline.reproject_to_wgs84.map! do |border|
|
56
|
+
border.with_properties("category" => "edge")
|
57
|
+
end.tap do |border|
|
57
58
|
collections << border
|
58
59
|
end
|
59
60
|
end.inject(&:merge)
|
@@ -85,33 +86,33 @@ module NSWTopo
|
|
85
86
|
font_size = label_params["font-size"]
|
86
87
|
offset = -0.85 * font_size
|
87
88
|
inset = INSET + font_size * 0.5 * Math::sin(@map.rotation.abs * Math::PI / 180)
|
88
|
-
inset_geometry = @map.neatline(mm: -inset)
|
89
|
+
inset_geometry = @map.neatline(mm: -inset).map!(&:remove_holes)
|
89
90
|
|
90
|
-
|
91
|
+
eastings, northings = features.select do |linestring|
|
91
92
|
linestring["label"]
|
92
|
-
end
|
93
|
-
eastings = gridlines.select do |gridline|
|
93
|
+
end.partition do |gridline|
|
94
94
|
gridline["category"] == "easting"
|
95
95
|
end
|
96
96
|
|
97
97
|
flip_eastings = eastings.partition do |easting|
|
98
|
-
Math::atan2(*easting.coordinates.values_at(0, -1).inject(
|
98
|
+
Math::atan2(*easting.coordinates.values_at(0, -1).inject(&:-)) * 180.0 / Math::PI > @map.rotation
|
99
99
|
end.map(&:length).inject(&:>)
|
100
|
+
|
100
101
|
eastings.each do |easting|
|
101
|
-
easting.coordinates.reverse!
|
102
102
|
easting["ends"].map! { |index| 1 - index }
|
103
|
-
end if flip_eastings
|
103
|
+
end.map!(&:reverse) if flip_eastings
|
104
104
|
|
105
|
-
|
105
|
+
eastings.concat(northings).inject(GeoJSON::Collection.new(projection: @map.neatline.projection)) do |collection, gridline|
|
106
106
|
collection << gridline.offset(offset, splits: false)
|
107
107
|
end.clip(inset_geometry).explode.flat_map do |gridline|
|
108
108
|
label, ends = gridline.values_at "label", "ends"
|
109
109
|
%i[itself reverse].values_at(*ends).map do |order|
|
110
110
|
text_length, text_path = label_element(label, label_params)
|
111
|
-
|
112
|
-
fraction = text_length /
|
113
|
-
|
114
|
-
|
111
|
+
p0, p1 = gridline.coordinates.send(order).take(2)
|
112
|
+
fraction = text_length / (p1 - p0).norm
|
113
|
+
p01 = p1 * fraction + p0 * (1 - fraction)
|
114
|
+
coordinates = [p0, p01].send(order)
|
115
|
+
GeoJSON::LineString[coordinates, "label" => text_path]
|
115
116
|
end
|
116
117
|
end
|
117
118
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module NSWTopo
|
2
|
+
module Labels
|
3
|
+
class Barriers
|
4
|
+
def initialize
|
5
|
+
@barriers, @cache = [], Hash[]
|
6
|
+
end
|
7
|
+
|
8
|
+
extend Forwardable
|
9
|
+
delegate :<< => :@barriers
|
10
|
+
|
11
|
+
def to_proc
|
12
|
+
@index ||= RTree.load(@barriers.flat_map(&:explode), &:bounds)
|
13
|
+
@proc ||= lambda do |label_hull, buffer|
|
14
|
+
@cache[[buffer, label_hull.coordinates]] ||= @index.search(label_hull.bounds, buffer).with_object Set[] do |barrier_hull, barriers|
|
15
|
+
next if barriers === barrier_hull.source
|
16
|
+
next unless ConvexHulls.overlap?(barrier_hull, label_hull, buffer)
|
17
|
+
barriers << barrier_hull.source
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|