nswtopo 2.0.0.pre.beta1 → 3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/COPYING +70 -83
  3. data/bin/nswtopo +227 -116
  4. data/docs/README.md +1 -12
  5. data/docs/add.md +1 -1
  6. data/docs/config.md +1 -1
  7. data/docs/contours.md +3 -1
  8. data/docs/init.md +8 -0
  9. data/docs/inspect.md +103 -0
  10. data/docs/move.md +9 -0
  11. data/docs/render.md +16 -7
  12. data/docs/scrape.md +67 -0
  13. data/docs/spot-heights.md +6 -2
  14. data/lib/nswtopo/archive.rb +50 -41
  15. data/lib/nswtopo/chrome.rb +227 -0
  16. data/lib/nswtopo/commands/add.rb +106 -0
  17. data/lib/nswtopo/commands/config.rb +38 -0
  18. data/lib/nswtopo/commands/inspect.rb +74 -0
  19. data/lib/nswtopo/commands/layers.rb +22 -0
  20. data/lib/nswtopo/commands/scrape.rb +79 -0
  21. data/lib/nswtopo/commands.rb +57 -0
  22. data/lib/nswtopo/dither.rb +5 -3
  23. data/lib/nswtopo/font.rb +46 -21
  24. data/lib/nswtopo/formats/gemf.rb +42 -0
  25. data/lib/nswtopo/formats/kmz.rb +26 -24
  26. data/lib/nswtopo/formats/mbtiles.rb +5 -41
  27. data/lib/nswtopo/formats/pdf.rb +82 -17
  28. data/lib/nswtopo/formats/svg.rb +114 -45
  29. data/lib/nswtopo/formats/svgz.rb +2 -2
  30. data/lib/nswtopo/formats/zip.rb +33 -23
  31. data/lib/nswtopo/formats.rb +77 -32
  32. data/lib/nswtopo/geometry/overlap.rb +1 -32
  33. data/lib/nswtopo/geometry/r_tree.rb +16 -10
  34. data/lib/nswtopo/geometry/segment.rb +3 -3
  35. data/lib/nswtopo/geometry/straight_skeleton/nodes.rb +5 -6
  36. data/lib/nswtopo/geometry/vector_sequence.rb +7 -6
  37. data/lib/nswtopo/gis/arcgis/connection.rb +56 -0
  38. data/lib/nswtopo/gis/arcgis/layer/map.rb +163 -0
  39. data/lib/nswtopo/gis/arcgis/layer/query.rb +87 -0
  40. data/lib/nswtopo/gis/arcgis/layer/renderer.rb +66 -0
  41. data/lib/nswtopo/gis/arcgis/layer/statistics.rb +15 -0
  42. data/lib/nswtopo/gis/arcgis/layer.rb +201 -0
  43. data/lib/nswtopo/gis/arcgis/service.rb +57 -0
  44. data/lib/nswtopo/gis/arcgis.rb +3 -0
  45. data/lib/nswtopo/gis/dem.rb +13 -12
  46. data/lib/nswtopo/gis/esri_hdr.rb +8 -2
  47. data/lib/nswtopo/gis/geojson/collection.rb +45 -21
  48. data/lib/nswtopo/gis/geojson/multi_line_string.rb +2 -24
  49. data/lib/nswtopo/gis/geojson/multi_polygon.rb +2 -53
  50. data/lib/nswtopo/gis/geojson/polygon.rb +15 -0
  51. data/lib/nswtopo/gis/geojson.rb +12 -3
  52. data/lib/nswtopo/gis/gps/kml.rb +25 -19
  53. data/lib/nswtopo/gis/gps.rb +2 -0
  54. data/lib/nswtopo/gis/projection.rb +35 -24
  55. data/lib/nswtopo/gis/shapefile.rb +89 -16
  56. data/lib/nswtopo/gis.rb +1 -2
  57. data/lib/nswtopo/helpers/array.rb +0 -11
  58. data/lib/nswtopo/helpers/colour.rb +34 -14
  59. data/lib/nswtopo/layer/arcgis_raster.rb +44 -48
  60. data/lib/nswtopo/layer/colour_mask.rb +5 -0
  61. data/lib/nswtopo/layer/contour.rb +35 -28
  62. data/lib/nswtopo/layer/control.rb +2 -7
  63. data/lib/nswtopo/layer/declination.rb +9 -9
  64. data/lib/nswtopo/layer/feature.rb +36 -22
  65. data/lib/nswtopo/layer/grid.rb +30 -27
  66. data/lib/nswtopo/layer/import.rb +1 -21
  67. data/lib/nswtopo/layer/labels/barrier.rb +39 -0
  68. data/lib/nswtopo/layer/labels.rb +551 -383
  69. data/lib/nswtopo/layer/mask_render.rb +37 -0
  70. data/lib/nswtopo/layer/overlay.rb +2 -2
  71. data/lib/nswtopo/layer/raster.rb +31 -41
  72. data/lib/nswtopo/layer/raster_import.rb +17 -0
  73. data/lib/nswtopo/layer/raster_render.rb +15 -0
  74. data/lib/nswtopo/layer/relief.rb +27 -95
  75. data/lib/nswtopo/layer/spot.rb +63 -62
  76. data/lib/nswtopo/layer/vector/cutout.rb +15 -0
  77. data/lib/nswtopo/layer/vector/knockout.rb +16 -0
  78. data/lib/nswtopo/layer/vector.rb +121 -89
  79. data/lib/nswtopo/layer/vegetation.rb +39 -34
  80. data/lib/nswtopo/layer.rb +30 -16
  81. data/lib/nswtopo/map.rb +202 -109
  82. data/lib/nswtopo/os.rb +5 -27
  83. data/lib/nswtopo/tiled_web_map.rb +54 -0
  84. data/lib/nswtopo/tree_indenter.rb +27 -0
  85. data/lib/nswtopo/version.rb +27 -2
  86. data/lib/nswtopo.rb +6 -199
  87. metadata +41 -22
  88. data/lib/nswtopo/font/chrome.rb +0 -59
  89. data/lib/nswtopo/font/generic.rb +0 -25
  90. data/lib/nswtopo/gis/arcgis_server/connection.rb +0 -52
  91. data/lib/nswtopo/gis/arcgis_server.rb +0 -155
  92. data/lib/nswtopo/gis/geojson/multi_point.rb +0 -12
  93. data/lib/nswtopo/gis/world_file.rb +0 -19
  94. data/lib/nswtopo/layer/labels/fence.rb +0 -20
@@ -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
- fence: 2.0
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 = 0.001 * @map.scale * @spacing
21
- row_spacing = 0.001 * @map.scale * @arrows * 0.5
22
- col_offset = 0.001 * @map.scale * (@offset % @spacing)
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, -radius], [x, radius]].map do |point|
32
- point.rotate_by_degrees(-declination).plus(@map.centre)
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(-declination).plus(@map.centre)
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 - 180 * Math::atan2(*line.difference.reverse) / Math::PI
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, ArcGISServer, Shapefile, Log
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[:fallback]
14
+ !args.delete(:fallback)
15
15
  end.map do |fallbacks|
16
- options, collection, error = fallbacks.inject [{}, nil, nil] do |(options, *), source: nil, fallback: false, **args|
17
- source = @path if @path
18
- log_update "%s: %s" % [@name, fallback ? "failed to retrieve features, trying fallback source" : "retrieving features"]
19
- raise "#{@source.basename}: no feature source defined" unless source
20
- options.merge! args
21
- break options, arcgis_layer(source, margin: MARGIN, **options.slice(:where, :layer, :per_page)) do |index, total|
22
- log_update "%s: retrieved %i of %i feature%s" % [@name, index, total, (?s if total > 1)]
23
- end if ArcGISServer === source
24
- source_path = Pathname(source).expand_path(@source.parent)
25
- break options, shapefile_layer(source_path, margin: MARGIN, **options.slice(:where, :sql, :layer)) if Shapefile === source_path
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
- raise error if error
32
- next collection.reproject_to(@map.projection), options
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]].map do |category|
49
+ categories = [*options[:category]].flat_map do |category|
41
50
  Hash === category ? [*category] : [category]
42
- end.flatten(1).map do |attribute, substitutions|
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 = 0.001 * (mm == true ? 5 : mm) * @map.scale
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 =~ /-labels$/
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
@@ -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
- boundary:
11
- stroke: gray
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.bounding_box).map do |zone|
28
- utm, utm_hull = Projection.utm(zone), Projection.utm_hull(zone)
29
- map_hull = @map.bounding_box(MARGIN).reproject_to_wgs84.coordinates.first
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.bounds(projection: utm).map do |min, max|
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!(utm_hull).clip!(map_hull).each do |linestring|
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!(utm_hull).coordinates.map(&:first)
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.flatten.inject(&:merge)
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(%w[%d %02d %03d]).map do |part, format|
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.flat_map do |text, percent|
74
- [Font.glyph_length(?\s, label_params), Font.glyph_length(text, label_params.merge("font-size" => font_size * percent / 100.0))]
75
- end.drop(1).sum
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 * @map.scale / 1000.0
86
+ offset = -0.85 * font_size
84
87
  inset = INSET + font_size * 0.5 * Math::sin(@map.rotation.abs * Math::PI / 180)
85
- inset_hull = @map.bounding_box(mm: -inset).coordinates.first
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).clip(inset_hull)
104
- end.compact.flat_map do |gridline|
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 * @map.scale / 1000.0 / segment.distance
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
@@ -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