nswtopo 2.0.0.pre.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/COPYING +674 -0
- data/bin/nswtopo +430 -0
- data/docs/README.md +78 -0
- data/docs/add.md +49 -0
- data/docs/config.md +24 -0
- data/docs/contours.md +37 -0
- data/docs/controls.md +9 -0
- data/docs/declination.md +15 -0
- data/docs/delete.md +15 -0
- data/docs/grid.md +5 -0
- data/docs/info.md +5 -0
- data/docs/init.md +38 -0
- data/docs/layers.md +11 -0
- data/docs/overlay.md +37 -0
- data/docs/relief.md +22 -0
- data/docs/render.md +43 -0
- data/docs/spot-heights.md +23 -0
- data/lib/nswtopo/archive.rb +93 -0
- data/lib/nswtopo/avl_tree.rb +128 -0
- data/lib/nswtopo/config.rb +73 -0
- data/lib/nswtopo/dither.rb +31 -0
- data/lib/nswtopo/font/chrome.rb +59 -0
- data/lib/nswtopo/font/generic.rb +25 -0
- data/lib/nswtopo/font.rb +43 -0
- data/lib/nswtopo/formats/kmz.rb +149 -0
- data/lib/nswtopo/formats/mbtiles.rb +64 -0
- data/lib/nswtopo/formats/pdf.rb +31 -0
- data/lib/nswtopo/formats/svg.rb +69 -0
- data/lib/nswtopo/formats/svgz.rb +13 -0
- data/lib/nswtopo/formats/zip.rb +40 -0
- data/lib/nswtopo/formats.rb +76 -0
- data/lib/nswtopo/geometry/overlap.rb +78 -0
- data/lib/nswtopo/geometry/r_tree.rb +47 -0
- data/lib/nswtopo/geometry/segment.rb +27 -0
- data/lib/nswtopo/geometry/straight_skeleton/collapse.rb +21 -0
- data/lib/nswtopo/geometry/straight_skeleton/interior_node.rb +17 -0
- data/lib/nswtopo/geometry/straight_skeleton/node.rb +50 -0
- data/lib/nswtopo/geometry/straight_skeleton/nodes.rb +295 -0
- data/lib/nswtopo/geometry/straight_skeleton/split.rb +33 -0
- data/lib/nswtopo/geometry/straight_skeleton/vertex.rb +9 -0
- data/lib/nswtopo/geometry/straight_skeleton.rb +6 -0
- data/lib/nswtopo/geometry/vector.rb +91 -0
- data/lib/nswtopo/geometry/vector_sequence.rb +179 -0
- data/lib/nswtopo/geometry.rb +8 -0
- data/lib/nswtopo/gis/arcgis_server/connection.rb +52 -0
- data/lib/nswtopo/gis/arcgis_server.rb +155 -0
- data/lib/nswtopo/gis/dem.rb +70 -0
- data/lib/nswtopo/gis/esri_hdr.rb +77 -0
- data/lib/nswtopo/gis/gdal_glob.rb +41 -0
- data/lib/nswtopo/gis/geojson/collection.rb +94 -0
- data/lib/nswtopo/gis/geojson/line_string.rb +11 -0
- data/lib/nswtopo/gis/geojson/multi_line_string.rb +63 -0
- data/lib/nswtopo/gis/geojson/multi_point.rb +12 -0
- data/lib/nswtopo/gis/geojson/multi_polygon.rb +167 -0
- data/lib/nswtopo/gis/geojson/point.rb +9 -0
- data/lib/nswtopo/gis/geojson/polygon.rb +11 -0
- data/lib/nswtopo/gis/geojson.rb +89 -0
- data/lib/nswtopo/gis/gps/gpx.rb +22 -0
- data/lib/nswtopo/gis/gps/kml.rb +66 -0
- data/lib/nswtopo/gis/gps.rb +20 -0
- data/lib/nswtopo/gis/projection.rb +56 -0
- data/lib/nswtopo/gis/shapefile.rb +24 -0
- data/lib/nswtopo/gis/world_file.rb +19 -0
- data/lib/nswtopo/gis.rb +9 -0
- data/lib/nswtopo/help_formatter.rb +59 -0
- data/lib/nswtopo/helpers/array.rb +30 -0
- data/lib/nswtopo/helpers/colour.rb +176 -0
- data/lib/nswtopo/helpers/concurrently.rb +27 -0
- data/lib/nswtopo/helpers/dir.rb +7 -0
- data/lib/nswtopo/helpers/hash.rb +15 -0
- data/lib/nswtopo/helpers/tar_writer.rb +11 -0
- data/lib/nswtopo/helpers.rb +6 -0
- data/lib/nswtopo/layer/arcgis_raster.rb +73 -0
- data/lib/nswtopo/layer/contour.rb +233 -0
- data/lib/nswtopo/layer/control.rb +94 -0
- data/lib/nswtopo/layer/declination.rb +53 -0
- data/lib/nswtopo/layer/feature.rb +87 -0
- data/lib/nswtopo/layer/grid.rb +120 -0
- data/lib/nswtopo/layer/import.rb +25 -0
- data/lib/nswtopo/layer/labels/fence.rb +20 -0
- data/lib/nswtopo/layer/labels.rb +630 -0
- data/lib/nswtopo/layer/overlay.rb +53 -0
- data/lib/nswtopo/layer/raster.rb +63 -0
- data/lib/nswtopo/layer/relief.rb +143 -0
- data/lib/nswtopo/layer/spot.rb +171 -0
- data/lib/nswtopo/layer/vector.rb +263 -0
- data/lib/nswtopo/layer/vegetation.rb +73 -0
- data/lib/nswtopo/layer.rb +78 -0
- data/lib/nswtopo/log.rb +28 -0
- data/lib/nswtopo/map.rb +296 -0
- data/lib/nswtopo/os.rb +75 -0
- data/lib/nswtopo/safely.rb +13 -0
- data/lib/nswtopo/version.rb +4 -0
- data/lib/nswtopo/zip.rb +15 -0
- data/lib/nswtopo.rb +249 -0
- metadata +142 -0
@@ -0,0 +1,167 @@
|
|
1
|
+
module NSWTopo
|
2
|
+
module GeoJSON
|
3
|
+
class MultiPolygon
|
4
|
+
include StraightSkeleton
|
5
|
+
|
6
|
+
def clip(hull)
|
7
|
+
polys = @coordinates.inject([]) do |result, rings|
|
8
|
+
lefthanded = rings.first.clockwise?
|
9
|
+
interior, exterior = hull.zip(hull.perps).inject(rings) do |rings, (vertex, perp)|
|
10
|
+
insides, neighbours, clipped = Hash[].compare_by_identity, Hash[].compare_by_identity, []
|
11
|
+
rings.each do |points|
|
12
|
+
points.map do |point|
|
13
|
+
point.minus(vertex).dot(perp) >= 0
|
14
|
+
end.segments.zip(points.segments).each do |inside, segment|
|
15
|
+
insides[segment] = inside
|
16
|
+
neighbours[segment] = [nil, nil]
|
17
|
+
end.map(&:last).ring.each do |segment0, segment1|
|
18
|
+
neighbours[segment1][0], neighbours[segment0][1] = segment0, segment1
|
19
|
+
end
|
20
|
+
end
|
21
|
+
neighbours.select! do |segment, _|
|
22
|
+
insides[segment].any?
|
23
|
+
end
|
24
|
+
insides.select do |segment, inside|
|
25
|
+
inside.inject(&:^)
|
26
|
+
end.each do |segment, inside|
|
27
|
+
segment[inside[0] ? 1 : 0] = segment.along(vertex.minus(segment[0]).dot(perp) / segment.difference.dot(perp))
|
28
|
+
end.sort_by do |segment, inside|
|
29
|
+
segment[inside[0] ? 1 : 0].minus(vertex).cross(perp) * (lefthanded ? -1 : 1)
|
30
|
+
end.map(&:first).each_slice(2) do |segment0, segment1|
|
31
|
+
segment = [segment0[1], segment1[0]]
|
32
|
+
neighbours[segment0][1] = neighbours[segment1][0] = segment
|
33
|
+
neighbours[segment] = [segment0, segment1]
|
34
|
+
end
|
35
|
+
while neighbours.any?
|
36
|
+
segment, * = neighbours.first
|
37
|
+
clipped << []
|
38
|
+
while neighbours.include? segment
|
39
|
+
clipped.last << segment[0]
|
40
|
+
*, segment = neighbours.delete(segment)
|
41
|
+
end
|
42
|
+
clipped.last << clipped.last.first
|
43
|
+
end
|
44
|
+
clipped
|
45
|
+
end.partition(&:clockwise?).rotate(lefthanded ? 1 : 0)
|
46
|
+
next result << exterior + interior if exterior.one?
|
47
|
+
exterior.inject(result) do |result, exterior_ring|
|
48
|
+
within, interior = interior.partition do |interior_ring|
|
49
|
+
interior_ring.first.within? exterior_ring
|
50
|
+
end
|
51
|
+
result << [exterior_ring, *within]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
polys.none? ? nil : polys.one? ? Polygon.new(*polys, @properties) : MultiPolygon.new(polys, @properties)
|
55
|
+
end
|
56
|
+
|
57
|
+
def area
|
58
|
+
@coordinates.flatten(1).sum(&:signed_area)
|
59
|
+
end
|
60
|
+
|
61
|
+
def skeleton
|
62
|
+
segments = []
|
63
|
+
Nodes.new(@coordinates.flatten(1)).progress do |event, node0, node1|
|
64
|
+
segments << [node0.point, node1.point].to_f
|
65
|
+
end
|
66
|
+
MultiLineString.new segments, @properties
|
67
|
+
end
|
68
|
+
|
69
|
+
def centres(fraction: 0.5, min_width: nil, interval:, lines: true)
|
70
|
+
neighbours = Hash.new { |neighbours, node| neighbours[node] = [] }
|
71
|
+
samples, tails, node1 = {}, {}, nil
|
72
|
+
|
73
|
+
Nodes.new(@coordinates.flatten(1)).progress(interval: interval) do |event, *args|
|
74
|
+
case event
|
75
|
+
when :nodes
|
76
|
+
node0, node1 = *args
|
77
|
+
neighbours[node0] << node1
|
78
|
+
neighbours[node1] << node0
|
79
|
+
when :interval
|
80
|
+
travel, rings = *args
|
81
|
+
samples[travel] = rings.flat_map do |ring|
|
82
|
+
ring.sample_at interval
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
samples[node1.travel] = [node1.point.to_f]
|
88
|
+
max_travel = neighbours.keys.map(&:travel).max
|
89
|
+
min_travel = [fraction * max_travel, min_width && 0.5 * min_width].compact.max
|
90
|
+
|
91
|
+
features = samples.select do |travel, points|
|
92
|
+
travel > min_travel
|
93
|
+
end.map do |travel, points|
|
94
|
+
MultiPoint.new points, @properties
|
95
|
+
end.reverse
|
96
|
+
return features unless lines
|
97
|
+
|
98
|
+
loop do
|
99
|
+
break unless neighbours.reject do |node, (neighbour, *others)|
|
100
|
+
others.any? || neighbours[neighbour].one?
|
101
|
+
end.each do |node, (neighbour, *)|
|
102
|
+
next if neighbours[neighbour].one?
|
103
|
+
neighbours.delete node
|
104
|
+
neighbours[neighbour].delete node
|
105
|
+
nodes, length = tails.delete(node) || [[node], 0]
|
106
|
+
candidate = [nodes << neighbour, length + [node.point, neighbour.point].distance]
|
107
|
+
tails[neighbour] = [tails[neighbour], candidate].compact.max_by(&:last)
|
108
|
+
end.any?
|
109
|
+
end
|
110
|
+
|
111
|
+
lengths, lines, candidates = Hash.new(0), Hash.new, tails.values
|
112
|
+
while candidates.any?
|
113
|
+
(*nodes, node), length = candidates.pop
|
114
|
+
next if (neighbours[node] - nodes).each do |neighbour|
|
115
|
+
candidates << [[*nodes, node, neighbour], length + [node.point, neighbour.point].distance]
|
116
|
+
end.any?
|
117
|
+
index = nodes.find(&:index).index
|
118
|
+
tail_nodes, tail_length = tails[node] || [[node], 0]
|
119
|
+
lengths[index], lines[index] = length + tail_length, nodes + tail_nodes.reverse if length + tail_length > lengths[index]
|
120
|
+
end
|
121
|
+
|
122
|
+
linestrings = lines.values.map do |nodes|
|
123
|
+
nodes.chunk do |node|
|
124
|
+
node.travel >= min_travel
|
125
|
+
end.select(&:first).map(&:last).reject(&:one?).map do |nodes|
|
126
|
+
nodes.map(&:point).to_f
|
127
|
+
end
|
128
|
+
end.flatten(1)
|
129
|
+
features.prepend MultiLineString.new(linestrings, @properties)
|
130
|
+
end
|
131
|
+
|
132
|
+
def centrepoints(interval:, **options)
|
133
|
+
centres(**options, interval: interval, lines: false)
|
134
|
+
end
|
135
|
+
|
136
|
+
def centrelines(**options)
|
137
|
+
centres(**options, interval: nil, lines: true)
|
138
|
+
end
|
139
|
+
|
140
|
+
def buffer(*margins, **options)
|
141
|
+
nodes = Nodes.new @coordinates.flatten(1)
|
142
|
+
margins.each do |margin|
|
143
|
+
nodes.progress limit: -margin, **options.slice(:rounding_angle, :cutoff_angle)
|
144
|
+
end
|
145
|
+
interior_rings, exterior_rings = nodes.readout.partition(&:hole?)
|
146
|
+
polygons, foo = exterior_rings.sort_by(&:signed_area).inject [[], interior_rings] do |(polygons, interior_rings), exterior_ring|
|
147
|
+
claimed, unclaimed = interior_rings.partition do |interior_ring|
|
148
|
+
interior_ring.first.within? exterior_ring
|
149
|
+
end
|
150
|
+
[polygons << [exterior_ring, *claimed], unclaimed]
|
151
|
+
end
|
152
|
+
MultiPolygon.new polygons.entries, @properties
|
153
|
+
end
|
154
|
+
|
155
|
+
def centroids
|
156
|
+
MultiPoint.new @coordinates.map(&:first).map(&:centroid), @properties
|
157
|
+
end
|
158
|
+
|
159
|
+
def samples(interval)
|
160
|
+
points = @coordinates.flatten(1).flat_map do |ring|
|
161
|
+
ring.sample_at interval
|
162
|
+
end
|
163
|
+
MultiPoint.new points, @properties
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require_relative 'geojson/collection'
|
2
|
+
|
3
|
+
module NSWTopo
|
4
|
+
module GeoJSON
|
5
|
+
Error = Class.new StandardError
|
6
|
+
TYPES = Set.new %W[Point MultiPoint LineString MultiLineString Polygon MultiPolygon]
|
7
|
+
|
8
|
+
CLASSES = TYPES.map do |type|
|
9
|
+
klass = Class.new do
|
10
|
+
def initialize(coordinates, properties = {})
|
11
|
+
properties ||= {}
|
12
|
+
raise Error, "invalid feature properties" unless Hash === properties
|
13
|
+
raise Error, "invalid feature geometry" unless Array === coordinates
|
14
|
+
@coordinates, @properties = coordinates, properties
|
15
|
+
end
|
16
|
+
attr_accessor :coordinates, :properties
|
17
|
+
|
18
|
+
define_method :to_h do
|
19
|
+
{
|
20
|
+
"type" => "Feature",
|
21
|
+
"geometry" => {
|
22
|
+
"type" => type,
|
23
|
+
"coordinates" => @coordinates
|
24
|
+
},
|
25
|
+
"properties" => @properties
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
extend Forwardable
|
30
|
+
delegate %i[[] []= fetch values_at key? store clear] => :@properties
|
31
|
+
end
|
32
|
+
|
33
|
+
const_set type, klass
|
34
|
+
end
|
35
|
+
|
36
|
+
CLASSES.zip(TYPES).each do |klass, type|
|
37
|
+
Collection.define_method "add_#{type}".downcase do |coordinates, properties = {}|
|
38
|
+
self << klass.new(coordinates, properties)
|
39
|
+
end
|
40
|
+
|
41
|
+
Collection.define_method "#{type}s".downcase do
|
42
|
+
grep klass
|
43
|
+
end
|
44
|
+
|
45
|
+
define_singleton_method type.downcase do |coordinates, projection: nil, properties: {}|
|
46
|
+
Collection.new(*projection) << klass.new(coordinates, properties)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
[[Point, MultiPoint ],
|
51
|
+
[LineString, MultiLineString],
|
52
|
+
[Polygon, MultiPolygon ]].each do |single_class, multi_class|
|
53
|
+
single_class.class_eval do
|
54
|
+
def explode
|
55
|
+
[self]
|
56
|
+
end
|
57
|
+
|
58
|
+
define_method :multi do
|
59
|
+
multi_class.new [@coordinates], @properties
|
60
|
+
end
|
61
|
+
|
62
|
+
delegate :clip => :multi
|
63
|
+
end
|
64
|
+
|
65
|
+
multi_class.class_eval do
|
66
|
+
define_method :explode do
|
67
|
+
@coordinates.map do |coordinates|
|
68
|
+
single_class.new coordinates, @properties
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def bounds
|
73
|
+
explode.map(&:bounds).transpose.map(&:flatten).map(&:minmax)
|
74
|
+
end
|
75
|
+
|
76
|
+
delegate :empty? => :@coordinates
|
77
|
+
|
78
|
+
alias multi dup
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
require_relative 'geojson/point'
|
85
|
+
require_relative 'geojson/line_string'
|
86
|
+
require_relative 'geojson/polygon'
|
87
|
+
require_relative 'geojson/multi_point'
|
88
|
+
require_relative 'geojson/multi_line_string'
|
89
|
+
require_relative 'geojson/multi_polygon'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module NSWTopo
|
2
|
+
class GPS
|
3
|
+
module GPX
|
4
|
+
def collection
|
5
|
+
GeoJSON::Collection.new.tap do |collection|
|
6
|
+
@xml.elements.each "/gpx/wpt" do |wpt|
|
7
|
+
coords = %w[lon lat].map { |name| wpt.attributes[name].to_f }
|
8
|
+
name = wpt.elements["name"]&.text
|
9
|
+
collection.add_point coords, "name" => name
|
10
|
+
end
|
11
|
+
@xml.elements.each "/gpx/trk" do |trk|
|
12
|
+
coords = trk.elements.collect("trkseg") do |trkseg|
|
13
|
+
trkseg.elements.collect("trkpt") { |trkpt| %w[lon lat].map { |name| trkpt.attributes[name].to_f } }
|
14
|
+
end
|
15
|
+
name = trk.elements["name"]&.text
|
16
|
+
collection.add_multilinestring coords, "name" => name
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module NSWTopo
|
2
|
+
class GPS
|
3
|
+
module KML
|
4
|
+
def styles(placemark)
|
5
|
+
style_url = placemark.elements["styleUrl"]&.text&.delete_prefix(?#)
|
6
|
+
style_element = @xml.elements["/kml/Document/[@id='%s']" % style_url]
|
7
|
+
result = {}
|
8
|
+
|
9
|
+
case style_element&.name
|
10
|
+
when "StyleMap"
|
11
|
+
style_element = style_element.elements["Pair[key[text()='normal']]"]
|
12
|
+
result = styles(style_element) if style_element
|
13
|
+
|
14
|
+
when "Style"
|
15
|
+
[%w[LineStyle stroke], %w[PolyStyle fill]].each do |element, attribute|
|
16
|
+
/^[0-9a-fA-F]{8}$/.match(style_element.elements["#{element}/color"]&.text) do |match|
|
17
|
+
a, b, g, r = match[0].each_char.each_slice(2).map(&:join)
|
18
|
+
result["#{attribute}-opacity"] = (Float("0x%s" % a) / 255).round(5)
|
19
|
+
result[attribute] = Colour.new("#%s%s%s" % [r, g, b]).to_s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
result["stroke"] ||= "#FFFFFF"
|
23
|
+
result["stroke-width"] = ((style_element.elements["LineStyle/width"]&.text || 1).to_f * 25.4 / 96.0).round(5)
|
24
|
+
|
25
|
+
[%w[fill fill], %w[outline stroke]].each do |element, attribute|
|
26
|
+
if style_element.elements["PolyStyle/#{element}"]&.text == ?0
|
27
|
+
result[attribute] = "none"
|
28
|
+
result.delete "#{attribute}-opacity"
|
29
|
+
result.delete "#{attribute}-width"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
result
|
34
|
+
end
|
35
|
+
|
36
|
+
def properties(placemark)
|
37
|
+
{ "name" => placemark.elements["name"]&.text, "folder" => placemark.elements["ancestor::Folder/name"]&.text }
|
38
|
+
end
|
39
|
+
|
40
|
+
def collection
|
41
|
+
GeoJSON::Collection.new.tap do |collection|
|
42
|
+
@xml.elements.each "/kml//Placemark[Point/coordinates]" do |placemark|
|
43
|
+
coords = placemark.elements["Point/coordinates"].text.split(',').take(2).map(&:to_f)
|
44
|
+
collection.add_point coords, properties(placemark).merge("styles" => {})
|
45
|
+
end
|
46
|
+
@xml.elements.each "/kml//Placemark[LineString/coordinates]" do |placemark|
|
47
|
+
coords = placemark.elements["LineString/coordinates"].text.split(' ').map { |triplet| triplet.split(',')[0..1].map(&:to_f) }
|
48
|
+
collection.add_linestring coords, properties(placemark).merge("styles" => styles(placemark))
|
49
|
+
end
|
50
|
+
@xml.elements.each "/kml//Placemark[gx:Track]" do |placemark|
|
51
|
+
coords = placemark.elements.collect("gx:Track/gx:coord") { |coord| coord.text.split(?\s).take(2).map(&:to_f) }
|
52
|
+
collection.add_linestring coords, properties(placemark).merge("styles" => styles(placemark))
|
53
|
+
end
|
54
|
+
@xml.elements.each "/kml//Placemark[Polygon/outerBoundaryIs/LinearRing/coordinates]" do |placemark|
|
55
|
+
coords = [placemark.elements["Polygon/outerBoundaryIs/LinearRing/coordinates"].text]
|
56
|
+
coords += placemark.elements.collect("Polygon/innerBoundaryIs/LinearRing/coordinates", &:text)
|
57
|
+
coords.map! do |text|
|
58
|
+
text.split(' ').map { |triplet| triplet.split(?,).take(2).map(&:to_f) }
|
59
|
+
end
|
60
|
+
collection.add_polygon coords, properties(placemark).merge("styles" => styles(placemark))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative 'gps/gpx'
|
2
|
+
require_relative 'gps/kml'
|
3
|
+
|
4
|
+
module NSWTopo
|
5
|
+
class GPS
|
6
|
+
def initialize(path)
|
7
|
+
@xml = REXML::Document.new(path.read)
|
8
|
+
case
|
9
|
+
when @xml.elements["/gpx"] then extend GPX
|
10
|
+
when @xml.elements["/kml"] then extend KML
|
11
|
+
else
|
12
|
+
raise "invalid GPX or KML file: #{path}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.load(path)
|
17
|
+
new(path).collection
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module NSWTopo
|
2
|
+
class Projection
|
3
|
+
def initialize(string_or_path)
|
4
|
+
@proj4 = OS.gdalsrsinfo("-o", "proj4", string_or_path).chomp.strip
|
5
|
+
raise "no georeferencing found: %s" % string_or_path if @proj4.empty?
|
6
|
+
end
|
7
|
+
|
8
|
+
%w[wkt wkt_simple wkt_noct wkt_esri mapinfo xml].each do |format|
|
9
|
+
define_method format do
|
10
|
+
OS.gdalsrsinfo("-o", format, @proj4).split(/['\r\n]+/).map(&:strip).join("")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :proj4
|
15
|
+
alias to_s proj4
|
16
|
+
alias to_str proj4
|
17
|
+
|
18
|
+
def ==(other)
|
19
|
+
proj4 == other.proj4
|
20
|
+
end
|
21
|
+
|
22
|
+
extend Forwardable
|
23
|
+
delegate :hash => :@proj4
|
24
|
+
alias eql? ==
|
25
|
+
|
26
|
+
def self.utm(zone, south = true)
|
27
|
+
new("+proj=utm +zone=#{zone}#{' +south' if south} +ellps=WGS84 +datum=WGS84 +units=m +no_defs")
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.wgs84
|
31
|
+
new("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.transverse_mercator(lon, lat)
|
35
|
+
new("+proj=tmerc +lon_0=#{lon} +lat_0=#{lat} +k=1 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs")
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.azimuthal_equidistant(lon, lat)
|
39
|
+
new("+proj=aeqd +lon_0=#{lon} +lat_0=#{lat} +k_0=1 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs")
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.utm_zones(collection)
|
43
|
+
collection.reproject_to_wgs84.bounds.first.map do |longitude|
|
44
|
+
(longitude / 6).floor + 31
|
45
|
+
end.yield_self do |min, max|
|
46
|
+
min..max
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.utm_hull(zone)
|
51
|
+
longitudes = [31, 30].map { |offset| (zone - offset) * 6.0 }
|
52
|
+
latitudes = [-80.0, 84.0]
|
53
|
+
longitudes.product(latitudes).values_at(0,2,3,1)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module NSWTopo
|
2
|
+
module Shapefile
|
3
|
+
Error = Class.new RuntimeError
|
4
|
+
|
5
|
+
def self.===(path)
|
6
|
+
OS.ogrinfo "-ro", "-so", path
|
7
|
+
true
|
8
|
+
rescue OS::Error
|
9
|
+
false
|
10
|
+
end
|
11
|
+
|
12
|
+
def shapefile_layer(shapefile_path, where: nil, sql: nil, layer: nil, margin: {})
|
13
|
+
raise "#{@source}: can't specify both SQL and where clause" if sql && where
|
14
|
+
raise "#{@source}: can't specify both SQL and layer name" if sql && layer
|
15
|
+
sql = ["-sql", sql] if sql
|
16
|
+
where = ["-where", "(" << Array(where).join(") AND (") << ")"] if where
|
17
|
+
srs = ["-t_srs", @map.projection]
|
18
|
+
spat = ["-spat", *@map.bounds(margin: margin).transpose.flatten, "-spat_srs", @map.projection]
|
19
|
+
misc = %w[-mapFieldType Date=Integer,DateTime=Integer -dim XY]
|
20
|
+
json = OS.ogr2ogr *(sql || where), *srs, *spat, *misc, "-f", "GeoJSON", "-lco", "RFC7946=NO", "/vsistdout/", shapefile_path, *layer
|
21
|
+
GeoJSON::Collection.load json, @map.projection
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module NSWTopo
|
2
|
+
module WorldFile
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def geotransform(top_left, resolution, angle)
|
6
|
+
sin, cos = Math::sin(angle * Math::PI / 180.0), Math::cos(angle * Math::PI / 180.0)
|
7
|
+
[[top_left[0], resolution * cos, resolution * sin],
|
8
|
+
[top_left[1], resolution * sin, -resolution * cos]]
|
9
|
+
end
|
10
|
+
|
11
|
+
def write(top_left, resolution, angle, path)
|
12
|
+
(x, r00, r01), (y, r10, r11) = geotransform(top_left, resolution, angle)
|
13
|
+
path.open("w") do |file|
|
14
|
+
file.puts r00, r01, r10, r11
|
15
|
+
file.puts x + 0.5 * resolution, y - 0.5 * resolution
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/nswtopo/gis.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require_relative 'gis/projection'
|
2
|
+
require_relative 'gis/geojson'
|
3
|
+
require_relative 'gis/gps'
|
4
|
+
require_relative 'gis/world_file'
|
5
|
+
require_relative 'gis/esri_hdr'
|
6
|
+
require_relative 'gis/arcgis_server'
|
7
|
+
require_relative 'gis/shapefile'
|
8
|
+
require_relative 'gis/gdal_glob'
|
9
|
+
require_relative 'gis/dem'
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class HelpFormatter < RDoc::Markup::ToAnsi
|
2
|
+
def initialize(ansi)
|
3
|
+
@ansi = ansi
|
4
|
+
super()
|
5
|
+
@headings.clear
|
6
|
+
return unless @ansi
|
7
|
+
@headings[1] = ["\e[1m", "\e[m"] # bold
|
8
|
+
@headings[2] = ["\e[4m", "\e[m"] # underline
|
9
|
+
@headings[3] = ["\e[3m", "\e[m"] # italic
|
10
|
+
end
|
11
|
+
|
12
|
+
def init_tags
|
13
|
+
return unless @ansi
|
14
|
+
add_tag :BOLD, "\e[1m", "\e[m" # bold
|
15
|
+
add_tag :EM, "\e[3m", "\e[m" # italic
|
16
|
+
add_tag :TT, "\e[3m", "\e[m" # italic
|
17
|
+
end
|
18
|
+
|
19
|
+
def accept_list_item_start(list_item)
|
20
|
+
@indent += 2
|
21
|
+
super
|
22
|
+
@res.pop if @res.last == ?\n
|
23
|
+
@indent -= 2
|
24
|
+
end
|
25
|
+
|
26
|
+
def accept_verbatim(verbatim)
|
27
|
+
indent = ?\s * (@indent + (@ansi ? 2 : 4))
|
28
|
+
verbatim.parts.map(&:each_line).flat_map(&:entries).each.with_index do |line, index|
|
29
|
+
case
|
30
|
+
when !@ansi
|
31
|
+
@res << indent << line
|
32
|
+
when line.start_with?("$ ")
|
33
|
+
@res << indent << "\e[90m$\e[;3m " << line[2..-1] << "\e[m"
|
34
|
+
else
|
35
|
+
@res << indent << "\e[90m> " << line << "\e[m"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
@res << ?\n
|
39
|
+
end
|
40
|
+
|
41
|
+
def accept_heading(heading)
|
42
|
+
@indent += 2 unless heading.level == 1
|
43
|
+
super
|
44
|
+
@indent -= 2 unless heading.level == 1
|
45
|
+
end
|
46
|
+
|
47
|
+
def accept_paragraph(paragraph)
|
48
|
+
@indent += 2
|
49
|
+
text = paragraph.text.tr_s(?\r, ?\s).tr_s(?\n, ?\s)
|
50
|
+
wrap attributes text
|
51
|
+
@indent -= 2
|
52
|
+
@res << ?\n
|
53
|
+
end
|
54
|
+
|
55
|
+
def start_accepting
|
56
|
+
super
|
57
|
+
@res = @ansi ? ["\e[0m"] : []
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module ArrayHelpers
|
2
|
+
def median
|
3
|
+
sort[length / 2]
|
4
|
+
end
|
5
|
+
|
6
|
+
def mean
|
7
|
+
empty? ? nil : inject(&:+) / length
|
8
|
+
end
|
9
|
+
|
10
|
+
def many?
|
11
|
+
length > 1
|
12
|
+
end
|
13
|
+
|
14
|
+
def in_two
|
15
|
+
each_slice(1 + [length - 1, 0].max / 2)
|
16
|
+
end
|
17
|
+
|
18
|
+
def nearby_pairs(closed = false, &block)
|
19
|
+
Enumerator.new do |yielder|
|
20
|
+
each.with_index do |element1, index|
|
21
|
+
(closed ? rotate(index) : drop(index)).drop(1).each do |element2|
|
22
|
+
break unless block.call [element1, element2]
|
23
|
+
yielder << [element1, element2]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
Array.send :include, ArrayHelpers
|