module NSWTopo::Vector
Constants
- ANGLE
- FONT_SCALED_ATTRIBUTES
- MARGIN
- SHIELD_Y
- SVG_ATTRIBUTES
Public Instance Methods
categorise(string)
click to toggle source
# File lib/nswtopo/layer/vector.rb, line 46 def categorise(string) string.tr_s('^_a-zA-Z0-9', ?-).delete_prefix(?-).delete_suffix(?-) end
create()
click to toggle source
# File lib/nswtopo/layer/vector.rb, line 9 def create @features = get_features.reproject_to(@map.projection).clip!(@map.bounding_box(MARGIN).coordinates.first) @map.write filename, @features.to_json end
drawing_features()
click to toggle source
# File lib/nswtopo/layer/vector.rb, line 29 def drawing_features features.explode.reject do |feature| feature["draw"] == false end end
features()
click to toggle source
# File lib/nswtopo/layer/vector.rb, line 18 def features @features ||= GeoJSON::Collection.load @map.read(filename) end
filename()
click to toggle source
# File lib/nswtopo/layer/vector.rb, line 14 def filename "#{@name}.json" end
labeling_features()
click to toggle source
# File lib/nswtopo/layer/vector.rb, line 35 def labeling_features features.select do |feature| feature["label"] end end
params_for(categories)
click to toggle source
# File lib/nswtopo/layer/vector.rb, line 72 def params_for(categories) params.select do |key, value| Array(key).any? do |selector| String(selector).split(?\s).to_set <= categories end end.values.inject(params, &:merge) end
render(group, defs) { |feature, buffer| ... }
click to toggle source
# File lib/nswtopo/layer/vector.rb, line 80 def render(group, defs) drawing_features.group_by do |feature, categories| categories || Array(feature["category"]).map(&:to_s).map(&method(:categorise)).to_set end.map do |categories, features| dupes = params_for(categories)["dupe"] Array(dupes).map(&:to_s).map do |dupe| [categories | Set[dupe], [name, *categories, "content"].join(?.)] end.push [categories, features] end.flatten(1).map do |categories, features| ids = [name, *categories] case features when String container = group.add_element "use", "class" => categories.to_a.join(?\s), "xlink:href" => "#%s" % features when Array container = group.add_element "g", "class" => categories.to_a.join(?\s) content = container.add_element "g", "id" => [*ids, "content"].join(?.) end container.add_attribute "id", ids.join(?.) if categories.any? commands = params_for categories font_size, bezier, section = commands.values_at "font-size", "bezier", "section" commands.slice(*FONT_SCALED_ATTRIBUTES).each do |key, value| commands[key] = commands[key].to_i * font_size * 0.01 if value =~ /^\d+%$/ end if font_size features.each do |feature, _| case feature when GeoJSON::Point symbol_id = [*ids, "symbol"].join(?.) transform = "translate(%s) rotate(%s)" % [POINT, ANGLE] % [*feature.coordinates.yield_self(&to_mm), feature.fetch("rotation", @map.rotation) - @map.rotation] content.add_element "use", "transform" => transform, "xlink:href" => "#%s" % symbol_id when GeoJSON::LineString linestring = feature.coordinates.map(&to_mm) (section ? linestring.in_sections(section) : [linestring]).each do |linestring| content.add_element "path", "fill" => "none", "d" => svg_path_data(linestring, bezier: bezier) end when GeoJSON::Polygon path_data = feature.coordinates.map do |ring| svg_path_data ring.map(&to_mm), bezier: bezier end.join(" Z ").concat(" Z") content.add_element "path", "fill-rule" => "nonzero", "d" => path_data when REXML::Element case feature.name when "text", "textPath" then content << feature when "path" then defs << feature end when Array content.add_element "path", "fill" => "none", "d" => svg_path_data(feature + feature.take(1)) end end if content commands.each do |command, args| next unless args args = args.map(&:to_a).inject([], &:+) if Array === args && args.all?(Hash) case command when "blur" filter_id = [*ids, "blur"].join(?.) container.add_attribute "filter", "url(#%s)" % filter_id defs.add_element("filter", "id" => filter_id).add_element "feGaussianBlur", "stdDeviation" => args, "in" => "SourceGraphic" when "opacity" if categories.none? group.add_attribute "style", "opacity:#{args}" else container.add_attribute "opacity", args end when "symbol" next unless content symbol = defs.add_element "g", "id" => [*ids, "symbol"].join(?.) args.each do |element, attributes| symbol.add_element element, attributes end when "pattern" dimensions, args = args.partition do |key, value| %w[width height].include? key end width, height = Hash[dimensions].values_at "width", "height" pattern_id = [*ids, "pattern"].join(?.) pattern = defs.add_element "pattern", "id" => pattern_id, "patternUnits" => "userSpaceOnUse", "width" => width, "height" => height args.each do |element, attributes| pattern.add_element element, attributes end container.add_attribute "fill", "url(#%s)" % pattern_id when "symbolise" next unless content interval, symbols = args.partition do |element, attributes| element == "interval" end interval = Hash[interval]["interval"] symbol_ids = symbols.map.with_index do |(element, attributes), index| symbol_id = [*ids, "symbol", index].join(?.).tap do |symbol_id| defs.add_element("g", "id" => symbol_id).add_element(element, attributes) end end lines_or_rings = features.grep(GeoJSON::LineString).map(&:coordinates) lines_or_rings += features.grep(GeoJSON::Polygon).map(&:coordinates).flatten(1) lines_or_rings.each do |points| points.map(&to_mm).sample_at(interval, angle: true).each do |point, angle| transform = "translate(%s) rotate(%s)" % [POINT, ANGLE] % [*point, 180.0 * angle / Math::PI] content.add_element "use", "transform" => transform, "xlink:href" => "#%s" % symbol_ids.sample end end when "inpoint", "outpoint", "endpoint" next unless content symbol_id = [*ids, command].join(?.) symbol = defs.add_element "g", "id" => symbol_id args.each do |element, attributes| symbol.add_element element, attributes end features.grep(GeoJSON::LineString).map do |feature| feature.coordinates.map(&to_mm) end.each do |line| case command when "inpoint" then [line.first(2)] when "outpoint" then [line.last(2).rotate] when "endpoint" then [line.first(2), line.last(2).rotate] end.each do |segment| transform = "translate(%s) rotate(%s)" % [POINT, ANGLE] % [*segment.first, 180.0 * segment.difference.angle / Math::PI] container.add_element "use", "transform" => transform, "xlink:href" => "#%s" % symbol_id end end when "mask" next unless args && content && content.elements.any? filter_id, mask_id = %w[raster-mask.filter raster-mask] mask_contents = defs.elements["mask[@id='%s']/g[@filter]" % mask_id] mask_contents ||= begin defs.add_element("filter", "id" => filter_id).add_element "feColorMatrix", "type" => "matrix", "in" => "SourceGraphic", "values" => "0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 -1 1" defs.add_element("mask", "id" => mask_id).add_element("g", "filter" => "url(#%s)" % filter_id).tap do |mask_contents| mask_contents.add_element "rect", "width" => "100%", "height" => "100%", "fill" => "none", "stroke" => "none" end end transforms = REXML::XPath.each(content, "ancestor::g[@transform]/@transform").map(&:value) mask_contents.add_element "use", "xlink:href" => "#%s" % content.attributes["id"], "transform" => (transforms.join(?\s) if transforms.any?) when "fence" next unless content && args buffer = 0.5 * (Numeric === args ? args : commands.fetch("stroke-width", 0)) features.each do |feature| next if REXML::Element === feature yield feature, buffer end when "shield" next unless content content.elements.each("text") do |element| next unless text_length = element.elements["./ancestor-or-self::[@textLength]/@textLength"]&.value&.to_f shield = REXML::Element.new("g") width, height = text_length + SHIELD_X * font_size, (1 + SHIELD_Y) * font_size shield.add_element "rect", "x" => -0.5 * width, "y" => -0.5 * height, "width" => width, "height" => height, "rx" => font_size * 0.3, "ry" => font_size * 0.3, "stroke" => "none", "fill" => args text_transform = element.attributes.get_attribute "transform" text_transform.remove shield.attributes << text_transform element.parent.elements << shield shield << element end when *SVG_ATTRIBUTES container.add_attribute command, args end end next categories, features, container end.tap do |categorised| params.fetch("order", []).reverse.map(&:split).map(&:to_set).each do |filter| categorised.select do |categories, features, container| filter <= categories end.reverse.each do |categories, features, container| group.unshift container.remove end end end end
svg_path_data(points, bezier: false)
click to toggle source
# File lib/nswtopo/layer/vector.rb, line 50 def svg_path_data(points, bezier: false) if bezier fraction = Numeric === bezier ? bezier.clamp(0.0, 1.0) : 1.0 extras = points.first == points.last ? [points[-2], *points, points[2]] : [points.first, *points, points.last] midpoints = extras.segments.map(&:midpoint) distances = extras.segments.map(&:distance) offsets = midpoints.zip(distances).segments.map(&:transpose).map do |segment, distance| segment.along(distance.first / distance.inject(&:+)) end.zip(points).map(&:difference) controls = midpoints.segments.zip(offsets).map do |segment, offset| segment.map { |point| [point, point.plus(offset)].along(fraction) } end.flatten(1).drop(1).each_slice(2).entries.prepend(nil) points.zip(controls).map do |point, controls| controls ? "C %s %s %s" % [POINT, POINT, POINT] % [*controls.flatten, *point] : "M %s" % POINT % point end.join(" ") else points.map do |point| POINT % point end.join(" L ").prepend("M ") end end
to_mm()
click to toggle source
# File lib/nswtopo/layer/vector.rb, line 25 def to_mm @to_mm ||= @map.method(:coords_to_mm) end
to_s()
click to toggle source
# File lib/nswtopo/layer/vector.rb, line 41 def to_s count = features.count "%s: %i feature%s" % [@name, count, (?s unless count == 1)] end