Canvas Scripting Layer (Mapping):
- first stab at refactoring the map.nas module, and trying to let the API evolve according to our requirements - split up the module into separate files (some of them will disappear soon) - split up the "drawing" loops into separate functions so that they can be individually called - move actual "drawing" to map_layers.nas - introduce some OOP helpers to prepare a pure Layer-based design - prepare helpers: LayeredMap, GenericMap, AirportMap (TODO: use a real "Layer" class) - move airport features (taxiways, runways, parking, tower) to separate layers (i.e. canvas groups) - avoid using a single update callback and use different layer-specific callbacks to update individual layers more efficiently - add some boilerplate hashes to prepare the MVC design - allow lazy updating of layers, where canvas groups are only populated on demand, to save some time during instantiation, i.e. loading an airport without "parking" selected, will only populate the layer once the checkbox is checked - extend the original code such that it supports showing multiple airports at once - add some proof of concept "navaid" layer using SVG files for navaid symbols (added only NDB symbol from wikimedia commons) regressions: - runway highlighting needs to be re-implemented - parking highlighting will be done differently - enforcing a specific drawing order for layers is currently not explicitly supported, so that taxiways may be rendered on top of runways Also: - integrated with the latest changes in git/master (HEAD) -i.e. metar support - further generalized map.nas - partially moved instantiation from Nasal space to XML space (WIP) - create "toggle layer" checkboxes procedurally in Nasal space - prepared the code to be better reusable in other dialogs (e.g. route manager, map dialog etc) - completely removed the "highlighting" (runway/parking) feature for now, because we talked about re-implementing it anyhow
This commit is contained in:
parent
2d202a4e7d
commit
e510c8917f
23 changed files with 1788 additions and 394 deletions
|
@ -60,5 +60,10 @@ var PropertyElement = {
|
|||
return node.getValue();
|
||||
else
|
||||
return default;
|
||||
}
|
||||
},
|
||||
getBool: func(key)
|
||||
{
|
||||
me._node.getNode(key, 1).getBoolValue();
|
||||
},
|
||||
|
||||
};
|
||||
|
|
|
@ -218,10 +218,13 @@ var Element = {
|
|||
{
|
||||
me.setBool("visible", visible);
|
||||
},
|
||||
getVisible: func me.getBool("visible"),
|
||||
# Hide element (Shortcut for setVisible(0))
|
||||
hide: func me.setVisible(0),
|
||||
# Show element (Shortcut for setVisible(1))
|
||||
show: func me.setVisible(1),
|
||||
# Toggle element visibility
|
||||
toggleVisibility: func me.setVisible( !me.getVisible() ),
|
||||
#
|
||||
setGeoPosition: func(lat, lon)
|
||||
{
|
||||
|
|
23
Nasal/canvas/design.txt
Normal file
23
Nasal/canvas/design.txt
Normal file
|
@ -0,0 +1,23 @@
|
|||
Nothing set in stone yet, we'll document things once the API becomes more stable and once it has been used in several dialogs and instruments
|
||||
|
||||
At the moment, this implements the notion of a "LayeredMap", a LayeredMap is a conventional Canvas Map which has support for easily managing "Layers",
|
||||
which are internally mapped to Canvas Groups. Each Group's "visible" property is managed by the LayeredMap, so that layers can be easily
|
||||
toggled on/off, i.e. via checkboxes.
|
||||
|
||||
Basically, the idea is this, we'll have a MVC (Model/View/Controller) setup, where:
|
||||
- the Model is mapped to the drawable's meta information (i.e. position)
|
||||
- the View is mapped to a conventional canvas group
|
||||
- the Controller is mapped to a bunch of property/timer callbacks to control the Model/View
|
||||
|
||||
|
||||
|
||||
|
||||
Model = PositionedSource
|
||||
View = Canvas
|
||||
Controller = control properties (zoom, range etc)
|
||||
|
||||
LayerElement = callback to create a canvas group
|
||||
Layer = canvas.Group
|
||||
|
||||
Map -> LayeredMap -> GenericMap -> AirportMap
|
||||
|
105
Nasal/canvas/generic-canvas-map.xml
Normal file
105
Nasal/canvas/generic-canvas-map.xml
Normal file
|
@ -0,0 +1,105 @@
|
|||
<?xml version="1.0"?>
|
||||
<!--
|
||||
generic-canvas.map XML:
|
||||
- to be used by dialogs and instruments to add a generic map (navaids, fixes, airports etc)
|
||||
- with each feature put on a separate layer (canvas group)
|
||||
- each layer being controllable via a boolean property
|
||||
|
||||
NOTE: This is still work in progress, and will be significantly refactored in the time to come
|
||||
|
||||
Current requirements: (these are subject to change)
|
||||
|
||||
Dialogs wanting to use this, MUST:
|
||||
|
||||
- set DIALOG_CANVAS in open block
|
||||
- provide a helper function dialog_property(p) to return a property appended to the dialog root in /sim/gui/dialogs/FOO/
|
||||
- to set up layer-checkboxes automatically, use canvas.GenericMap.setupGUICheckboxes(DIALOG_CANVAS, gui_group)
|
||||
|
||||
For example, add this to your dialogs Nasal/open block in "foo.xml":
|
||||
var dialog_name = "foo";
|
||||
var dialog_property = func(p) return "/sim/gui/dialogs/foo/"~p;
|
||||
var DIALOG_CANVAS = gui.findElementByName(cmdarg(), "airport-selection");
|
||||
canvas.GenericMap.setupGUICheckboxes(DIALOG_CANVAS, "canvas-control");
|
||||
|
||||
TODO: use a single "InitCanvasMapSupport();" helper
|
||||
|
||||
In the close block, you'll want to call "map.cleanup_listeners()" at the moment
|
||||
|
||||
-->
|
||||
<PropertyList>
|
||||
<!--FIXME: move somewhere else, this is GUI specific and not useful for canvas maps shown as instruments! -->
|
||||
<checkbox-toggle-template>
|
||||
<name></name>
|
||||
<label></label>
|
||||
<property></property>
|
||||
<binding>
|
||||
<command>dialog-apply</command>
|
||||
<object-name></object-name>
|
||||
</binding>
|
||||
</checkbox-toggle-template>
|
||||
|
||||
<!-- will be procedurally added to the dialog -->
|
||||
<zoom-template>
|
||||
<button>
|
||||
<name>zoomout</name>
|
||||
<legend>-</legend>
|
||||
<pref-width>22</pref-width>
|
||||
<pref-height>22</pref-height>
|
||||
|
||||
<binding>
|
||||
<command>property-adjust</command>
|
||||
<property></property>
|
||||
<min>0</min>
|
||||
<step>-1</step>
|
||||
</binding>
|
||||
</button>
|
||||
|
||||
<text>
|
||||
<label>MMMM</label>
|
||||
<halign>center</halign>
|
||||
<format>Zoom %d</format>
|
||||
<property></property>
|
||||
<live>true</live>
|
||||
</text>
|
||||
|
||||
<button>
|
||||
<name>zoomin</name>
|
||||
<legend>+</legend>
|
||||
<pref-width>22</pref-width>
|
||||
<pref-height>22</pref-height>
|
||||
|
||||
<binding>
|
||||
<command>property-adjust</command>
|
||||
<property></property>
|
||||
<step>1</step>
|
||||
<max></max> <!-- FIXME: compute dynamically via Nasal size() or just a property-->
|
||||
</binding>
|
||||
</button>
|
||||
|
||||
<empty><stretch>true</stretch></empty>
|
||||
|
||||
</zoom-template>
|
||||
|
||||
<nasal>
|
||||
<load><![CDATA[
|
||||
var my_canvas = canvas.get(cmdarg());
|
||||
my_canvas.setColorBackground(0.2, 0.5, 0.2, 0.5); #TODO: support customization in XML
|
||||
|
||||
var root = my_canvas.createGroup();
|
||||
# the top level AirportMap element uses a "GenericMap" now:
|
||||
|
||||
#TODO: features should be procedurally enabled via params (WIP)
|
||||
#TODO: use generic Map and instantiate via XML
|
||||
var map = canvas.GenericMap.new(parent:root, name:dialog_name) # FIXME: We shouldn't be using AirportMap here:
|
||||
# we need a high level wrapper that can instantiate
|
||||
# all sorts of maps, not just AirportMaps
|
||||
.setTranslation(300, 200) # TODO: move to Map class ctor!
|
||||
.setupZoom( dialog:DIALOG_CANVAS ) # TODO: make zooming configurable for non GUI use
|
||||
.pickupFeatures (DIALOG_CANVAS); # set up the features specified in the XML file
|
||||
|
||||
# FIXME: resource cleanup (listeners!)
|
||||
update_info();
|
||||
]]>
|
||||
</load>
|
||||
</nasal>
|
||||
</PropertyList>
|
|
@ -1,5 +1,56 @@
|
|||
###
|
||||
# map.nas - provide a high level method to create typical maps in FlightGear (airports, navaids, fixes and waypoints) for both, the GUI and instruments
|
||||
# implements the notion of a "layer" by using canvas groups and adding geo-referenced elements to a layer
|
||||
# layered maps are linked to boolean properties so that visibility can be easily toggled (GUI checkboxes or cockpit hotspots)
|
||||
# without having to redraw other layers
|
||||
#
|
||||
# GOALS: have a single Nasal/Canvas wrapper for all sort of maps in FlightGear, that can be easily shared and reused for different purposes
|
||||
#
|
||||
# DESIGN: ... is slowly evolving, but still very much beta for the time being
|
||||
#
|
||||
# API: not yet documented, but see eventually design.txt (will need to add doxygen-strings then)
|
||||
#
|
||||
# PERFORMANCE: will be improved, probabaly by moving some features to C++ space and optimizing things there
|
||||
#
|
||||
#
|
||||
# ISSUES: just look for the FIXME and TODO strings - currently, the priority is to create an OOP/MVC design with less specialized code in XML files
|
||||
#
|
||||
#
|
||||
# ROADMAP: Generalize this further, so that:
|
||||
#
|
||||
# - it can be easily reused
|
||||
# - use a MVC approach, where layer-specific data is provided by a Model object
|
||||
# - other dialogs can use this without tons of custom code (airports.xml, route-manager.xml, map-canvas.xml)
|
||||
# - generalize this further so that it can be used by instruments
|
||||
# - implement additional layers (tcas, wxradar, agradar) - especially expose the required data to Nasal
|
||||
# - implement better GUI support (events) so that zooming/panning via mouse can be supported
|
||||
# - make the whole thing styleable
|
||||
#
|
||||
# - keep track of things getting added here and decide if they should better move to the core canvas module or the C++ code
|
||||
#
|
||||
#
|
||||
# C++ RFEs:
|
||||
# - overload findNavaidsWithinRange() to support an optional position argument, so that arbitrary navaids can be looked up
|
||||
# - add Nasal extension function to get scenery vector data (landclass)
|
||||
# -
|
||||
# -
|
||||
#
|
||||
|
||||
# Mapping from surface codes to
|
||||
|
||||
|
||||
var DEBUG=0;
|
||||
if (DEBUG) {
|
||||
var benchmark = debug.benchmark;
|
||||
|
||||
}
|
||||
|
||||
else {
|
||||
var benchmark = func(label, code) code(); # NOP
|
||||
}
|
||||
|
||||
var assert = func(label, expr) expr and die(label);
|
||||
|
||||
# Mapping from surface codes to #TODO: make this XML-configurable
|
||||
var SURFACECOLORS = {
|
||||
1 : { type: "asphalt", r:0.2, g:0.2, b:0.2 },
|
||||
2 : { type: "concrete", r:0.3, g:0.3, b:0.3 },
|
||||
|
@ -14,6 +65,25 @@ var SURFACECOLORS = {
|
|||
0 : { type: "gravel", r:0.35, g:0.3, b:0.3 },
|
||||
};
|
||||
|
||||
|
||||
###
|
||||
# ALL LayeredMap "draws" go through this wrapper, which makes it easy to check what's going on:
|
||||
var draw_layer = func(layer, callback, lod) {
|
||||
var name= layer._view.get("id");
|
||||
# print("Canvas:Draw op triggered"); # just to make sure that we are not adding unnecessary data when checking/unchecking a checkbox
|
||||
if (DEBUG and name=="taxiways") fgcommand("profiler-start"); #without my patch, this is a no op, so no need to disable
|
||||
#print("Work items:", size(layer._model._elements));
|
||||
benchmark("Drawing Layer:"~layer._view.get("id"), func
|
||||
foreach(var element; layer._model._elements) {
|
||||
#print(typeof(layer._view));
|
||||
#debug.dump(layer._view);
|
||||
callback(layer._view, element, lod); # ISSUE here
|
||||
});
|
||||
if (! layer._model.hasData() ) print("Layer was EMPTY:", name);
|
||||
if (DEBUG and name=="taxiways") fgcommand("profiler-stop");
|
||||
layer._drawn=1; #TODO: this should be encapsulated
|
||||
}
|
||||
|
||||
# Runway
|
||||
#
|
||||
var Runway = {
|
||||
|
@ -45,209 +115,466 @@ var Runway = {
|
|||
}
|
||||
};
|
||||
|
||||
# AirportMap
|
||||
var make = func return {parents:arg};
|
||||
|
||||
##
|
||||
# TODO: Create a cache to reuse layers and layer data (i.e. runways)
|
||||
|
||||
|
||||
##
|
||||
# Todo: wrap parsesvg and return a function that memoizes the created canvas group, so that svg files only need to be parsed once
|
||||
#
|
||||
var AirportMap = {
|
||||
# Create AirportMap from hash
|
||||
#
|
||||
# @param apt Hash containing airport data as returned from airportinfo()
|
||||
# @param rwy Whether to display runways (default=1)
|
||||
# @param taxi Whether to display taxiways (default=1)
|
||||
# @param park Whether to display parking positions (default = 1)
|
||||
# @param twr Whether to display tower positions (default = 1)
|
||||
new: func(apt, rwy=1, taxi=1, park=1, twr=1)
|
||||
{
|
||||
return {
|
||||
parents: [AirportMap],
|
||||
_apt: apt,
|
||||
_display_runways: rwy,
|
||||
_display_taxiways: taxi,
|
||||
_display_parking: park,
|
||||
_display_tower: twr,
|
||||
};
|
||||
},
|
||||
# Build the graphical representation of the represented airport
|
||||
#
|
||||
# @param layer_runways canvas.Group to attach airport map to
|
||||
build: func(layer_runways)
|
||||
{
|
||||
var rws_done = {};
|
||||
|
||||
me.grp_apt = layer_runways.createChild("group", "apt-" ~ me._apt.id);
|
||||
|
||||
# Taxiways drawn first so the runways and parking positions end up on top.
|
||||
if (me._display_taxiways)
|
||||
{
|
||||
foreach(var taxi; me._apt.taxiways)
|
||||
{
|
||||
var clr = SURFACECOLORS[taxi.surface];
|
||||
if (clr == nil) { clr = SURFACECOLORS[0]};
|
||||
##
|
||||
# TODO: Implement a real MVC design for "LayeredMaps" that have:
|
||||
# - a "DataProvider" (i.e. Positioned objects)
|
||||
# - a View (i.e. a Canvas)
|
||||
# - a controller (i.e. input/output properties)
|
||||
#
|
||||
var MapModel = {}; # navaids, waypoints, fixes etc
|
||||
MapModel.new = func make(MapModel);
|
||||
|
||||
var MapView = {}; # the canvas view, including a layer for each feature
|
||||
MapView.new = func make(MapView);
|
||||
|
||||
var MapController = {}; # the property tree interface to manipulate the model/view via properties
|
||||
MapController.new = func make(MapController);
|
||||
|
||||
var LazyView = {}; # Gets drawables on demand from the model - via property toggle
|
||||
|
||||
var DataProvider = {};
|
||||
DataProvider.new = func make(DataProvider);
|
||||
|
||||
###
|
||||
# for airports, navaids, fixes, waypoints etc
|
||||
var PositionedProvider = {};
|
||||
PositionedProvider.new = func make(DataProvider, PositionedProvider);
|
||||
|
||||
##
|
||||
# Drawable
|
||||
#
|
||||
|
||||
## LayerElement (UNUSED ATM):
|
||||
# for runways, navaids, fixes, waypoints etc
|
||||
# TODO: we should differentiate between "fairly static" vs. "dynamic" layers - i.e. navaids vs. traffic
|
||||
var LayerElement = {_drawable:nil};
|
||||
LayerElement.new = func(drawable) {
|
||||
var temp = make(LayerElement);
|
||||
temp._drawable=drawable;
|
||||
return temp;
|
||||
}
|
||||
# a drawable is either a Nasal callback or a scalar, i.e. a path to an SVG file
|
||||
LayerElement.draw = func(group) {
|
||||
(typeof(me._drawable)=='func') and drawable(group) or canvas.parsesvg(group,_drawable);
|
||||
}
|
||||
|
||||
# For static targets like Navaids, Fixes - i.e. geographic position doesn't change
|
||||
var StaticLayerElement = {};
|
||||
|
||||
# For moving targets such as aircraft, multiplayer, ai traffic etc
|
||||
var DynamicLayerElement = {};
|
||||
|
||||
var AnimatedLayerElement = {};
|
||||
|
||||
# for elements whose appearance may change depending on selected range (i.e. LOD)
|
||||
var RangeAwareLayerElement = {};
|
||||
|
||||
|
||||
##
|
||||
# A layer model is just a wrapper for a vector with elements
|
||||
# either updated via a timer or via a listener
|
||||
|
||||
var LayerModel = {_elements:[], _view:, _controller: };
|
||||
LayerModel.new = func make(LayerModel);
|
||||
LayerModel.clear = func me._elements = [];
|
||||
LayerModel.push = func (e) append(me._elements, e);
|
||||
LayerModel.get = func me._elements;
|
||||
LayerModel.update = func;
|
||||
LayerModel.hasData = func size(me. _elements);
|
||||
LayerModel.setView = func(v) me._view=v;
|
||||
LayerModel.setController = func(c) me._controller=c;
|
||||
|
||||
|
||||
var LayerController = {};
|
||||
LayerController.new = func make(LayerController);
|
||||
|
||||
##
|
||||
# use timers to update the model/view (canvas)
|
||||
var TimeBasedLayerController = {};
|
||||
LayerController.new = func make(TimeBasedLayerController);
|
||||
|
||||
##
|
||||
# use listeners to update the model/view (canvas)
|
||||
#
|
||||
var ListenerBasedLayerController = {};
|
||||
ListenerBasedLayerController.new = func make(ListenerBasedLayerController);
|
||||
|
||||
|
||||
##
|
||||
# Uses, both, listeners and timers to update the model/view (canvas)
|
||||
#
|
||||
|
||||
var HybridLayerController = {};
|
||||
HybridLayerController.new = func make(HybridLayerController);
|
||||
|
||||
var ModelEvents = {INIT:, RESET:, UPDATE:};
|
||||
var ViewEvents = {INIT:, RESET:, UPDATE:};
|
||||
var ControllerEvents = {INIT:, RESET: , UPDATE:, ZOOM:, PAN:, };
|
||||
|
||||
|
||||
##
|
||||
# A layer is mapped to a canvas group
|
||||
# Layers are linked to a single boolean property to toggle them on/off
|
||||
var Layer = { _model: ,
|
||||
_view: ,
|
||||
_controller: ,
|
||||
_drawn:0,
|
||||
};
|
||||
|
||||
Layer.new = func(group, name, model) {
|
||||
#print("Setting up new Layer:", name);
|
||||
var m = make(Layer);
|
||||
m._model = model.new();
|
||||
#print("Model name is:", m._model.name);
|
||||
m._view = group.createChild("group",name);
|
||||
m.name = name; #FIXME: not needed, there's already _view.get("id")
|
||||
return m;
|
||||
}
|
||||
|
||||
Layer.hide = func me._view.setVisible(0);
|
||||
Layer.show = func me._view.setVisible(1);
|
||||
#TODO: Unify toggle and update methods - and support lazy drawing (make it optional!)
|
||||
Layer.toggle = func {
|
||||
# print("Toggling layer");
|
||||
var checkbox = getprop(me.display_layer);
|
||||
if(checkbox and !me._drawn) {
|
||||
# print("Lazy drawing");
|
||||
me.draw();
|
||||
}
|
||||
|
||||
#var state= me._view.getBool("visible");
|
||||
#print("Toggle layer visibility ",me.display_layer," checkbox is", checkbox);
|
||||
#print("Layer id is:", me._view.get("id"));
|
||||
#print("Drawn is:", me._drawn);
|
||||
checkbox?me._view.setVisible(1) : me._view.setVisible(0);
|
||||
}
|
||||
Layer.reset = func {
|
||||
me._view.removeAllChildren(); # clear the "real" canvas drawables
|
||||
me._model.clear(); # the vector is used for lazy rendering
|
||||
assert("Model not emptied during layer reset!", me._model.hasData() );
|
||||
me._drawn = 0;
|
||||
}
|
||||
#TODO: Unify toggle and update
|
||||
Layer.update = func {
|
||||
# print("Layer update: Check if layer is visible, if so, draw");
|
||||
if (! getprop(me.display_layer)) return; # checkbox for layer not set
|
||||
if (!me._model.hasData() ) return; # no data available
|
||||
# print("Trying to draw");
|
||||
me.draw();
|
||||
}
|
||||
|
||||
Layer.setDraw = func(callback) me.draw = callback;
|
||||
Layer.setController = func(c) me._controller=c; # TODO: implement
|
||||
Layer.setModel = func(m) nil; # TODO: implement
|
||||
|
||||
|
||||
##TODO: differentiate between layers with a single object (i.e. aircraft) and multiple objects (airports)
|
||||
|
||||
##
|
||||
# We may need to display some stuff that isn't strictly a geopgraphic feature, but just a chart feature
|
||||
#
|
||||
var CartographicLayer = {};
|
||||
|
||||
#TODO:
|
||||
var InteractiveLayer = {};
|
||||
|
||||
###
|
||||
# PositionedLayer
|
||||
#
|
||||
# layer of positioned objects (i.e. have lat,lon,alt)
|
||||
#
|
||||
var PositionedLayer = {};
|
||||
PositionedLayer.new = func() {
|
||||
make( Layer.new() , PositionedLayer );
|
||||
}
|
||||
|
||||
|
||||
###
|
||||
# CachedLayer
|
||||
#
|
||||
# when re-centering on an airport already loaded, we don't want to reload it
|
||||
# but change the reference point and load missing airports
|
||||
|
||||
var CachedLayer = {};
|
||||
|
||||
##
|
||||
#
|
||||
var AirportProvider = {};
|
||||
AirportProvider.new = func make(AirportProvider);
|
||||
AirportProvider.get = func {
|
||||
return airportinfo("ksfo");
|
||||
}
|
||||
|
||||
### Data Providers (preparation for MVC version):
|
||||
# TODO: should use the LayerModel class
|
||||
#
|
||||
|
||||
##
|
||||
# Manage a bunch of layers
|
||||
#
|
||||
|
||||
var LayerManager = {};
|
||||
|
||||
# WXR ?
|
||||
|
||||
# TODO: Stub
|
||||
var MapBehavior = {};
|
||||
MapBehavior.new = make(MapBehavior);
|
||||
MapBehavior.zoom = func;
|
||||
MapBehavior.center = func;
|
||||
|
||||
##
|
||||
# A layered map consists of several layers
|
||||
# TODO: Support nested LayeredMaps, where a LayeredMap may contain other LayeredMaps
|
||||
# TODO: use MapBehavior here and move the zoom/refpos methods there, so that map behavior can be easily customized
|
||||
var LayeredMap = { ranges:[],
|
||||
zoom_property:nil, listeners:[],
|
||||
update_property:nil, layers:[],
|
||||
};
|
||||
LayeredMap.new = func(parent, name)
|
||||
return make(LayeredMap, parent.createChild("map",name) );
|
||||
|
||||
LayeredMap.listen = func(p,c) { #FIXME: listening should be managed by each m/v/c separately
|
||||
# print("Setting up LayeredMap-managed listener:", p);
|
||||
append(me.listeners, setlistener(p, c));
|
||||
}
|
||||
|
||||
LayeredMap.initializeLayers = func {
|
||||
# print("initializing all layers and updating");
|
||||
foreach(var l; me.layers)
|
||||
l.update();
|
||||
}
|
||||
|
||||
LayeredMap.setRefPos = func(lat, lon) {
|
||||
# print("RefPos set");
|
||||
me._node.getNode("ref-lat", 1).setDoubleValue(lat);
|
||||
me._node.getNode("ref-lon", 1).setDoubleValue(lon);
|
||||
me; # chainable
|
||||
}
|
||||
LayeredMap.setHdg = func(hdg) {
|
||||
me._node.getNode("hdg",1).setDoubleValue(hdg);
|
||||
me; # chainable
|
||||
}
|
||||
|
||||
LayeredMap.updateZoom = func {
|
||||
var z = getprop(me.zoom_property) or 0;
|
||||
var zoom = me.ranges[ size(me.ranges)-1 -z];
|
||||
# print("Setting zoom range to:", zoom);
|
||||
benchmark("Zooming map:"~zoom, func
|
||||
me._node.getNode("range", 1).setDoubleValue(zoom)
|
||||
);
|
||||
me; #chainable
|
||||
}
|
||||
|
||||
# this is a huge hack at the moment, we need to encapsulate the setRefPos/setHdg methods, so that they are exposed to XML space
|
||||
#
|
||||
LayeredMap.updateState = func {
|
||||
# center map on airport TODO: should be moved to a method and wrapped with a controller so that behavior can be customizeda
|
||||
#var apt = me.layers[0]._model._elements[0];
|
||||
# FIXME:
|
||||
#me.setRefPos(lat:me._refpos.lat, lon:me._refpos.lon);
|
||||
|
||||
me.setHdg(0.0);
|
||||
me.updateZoom();
|
||||
}
|
||||
|
||||
#
|
||||
# TODO: this is currently GUI specific and not re-usable for instruments
|
||||
LayeredMap.setupZoom = func(dialog) {
|
||||
var dlgroot = dialog.getNode("features/dialog-root").getValue();#FIXME: GUI specific - needs to be re-implemented for instruments
|
||||
var zoom_property = dlgroot ~"/"~dialog.getNode("features/range-property").getValue(); #FIXME: this doesn't belong here, need to be in ctor instead !!!
|
||||
ranges=dialog.getNode("features/ranges").getChildren("range");
|
||||
foreach(var r; ranges)
|
||||
append(me.ranges, r.getValue() );
|
||||
|
||||
# print("Setting up Zoom Ranges:", size(ranges)-1);
|
||||
me.zoom_property=zoom_property;
|
||||
me.listen(zoom_property, func me.updateZoom() );
|
||||
me.updateZoom();
|
||||
me; #chainable
|
||||
}
|
||||
LayeredMap.setZoom = func {} #TODO
|
||||
|
||||
LayeredMap.resetLayers = func {
|
||||
|
||||
benchmark("Resetting LayeredMap", func
|
||||
foreach(var l; me.layers) { #TODO: hide all layers, hide map
|
||||
l.reset();
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
|
||||
#FIXME: listener management should be done at the MVC level, for each component - not as part of the LayeredMap!
|
||||
LayeredMap.cleanup_listeners = func {
|
||||
print("Cleaning up listeners");
|
||||
foreach(var l; me.listeners)
|
||||
removelistener(l);
|
||||
|
||||
var icon_taxi =
|
||||
me.grp_apt.createChild("path", "taxi")
|
||||
.setStrokeLineWidth(0)
|
||||
.setColor(clr.r, clr.g, clr.b)
|
||||
.setColorFill(clr.r, clr.g, clr.b);
|
||||
}
|
||||
|
||||
var txi = Runway.new(taxi);
|
||||
var beg1 = txi.pointOffCenterline(0, 0.5 * taxi.width);
|
||||
var beg2 = txi.pointOffCenterline(0, -0.5 * taxi.width);
|
||||
var end1 = txi.pointOffCenterline(taxi.length, 0.5 * taxi.width);
|
||||
var end2 = txi.pointOffCenterline(taxi.length, -0.5 * taxi.width);
|
||||
###
|
||||
# GenericMap: A generic map is a layered map that puts all supported features on a different layer (canvas group) so that
|
||||
# they can be individually toggled on/off so that unnecessary updates are avoided, there are methods to link layers to boolean properties
|
||||
# so that they can be easily associated with GUI properties (checkboxes) or cockpit hotspots
|
||||
# TODO: generalize the XML-parametrization and move it to a helper class
|
||||
|
||||
icon_taxi.setDataGeo
|
||||
(
|
||||
[ canvas.Path.VG_MOVE_TO,
|
||||
canvas.Path.VG_LINE_TO,
|
||||
canvas.Path.VG_LINE_TO,
|
||||
canvas.Path.VG_LINE_TO,
|
||||
canvas.Path.VG_CLOSE_PATH ],
|
||||
[ beg1[0], beg1[1],
|
||||
beg2[0], beg2[1],
|
||||
end2[0], end2[1],
|
||||
end1[0], end1[1] ]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (me._display_runways)
|
||||
{
|
||||
foreach(var rw; keys(me._apt.runways))
|
||||
{
|
||||
var is_heli = substr(rw, 0, 1) == "H";
|
||||
var rw_dir = is_heli ? nil : int(substr(rw, 0, 2));
|
||||
var GenericMap = { };
|
||||
GenericMap.new = func(parent, name) make(LayeredMap.new(parent:parent, name:name), GenericMap);
|
||||
|
||||
var rw_rec = "";
|
||||
var thresh_rec = 0;
|
||||
if( rw_dir != nil )
|
||||
{
|
||||
rw_rec = sprintf("%02d", math.mod(rw_dir - 18, 36));
|
||||
if( size(rw) == 3 )
|
||||
{
|
||||
var map_rec = {
|
||||
"R": "L",
|
||||
"L": "R",
|
||||
"C": "C"
|
||||
};
|
||||
rw_rec ~= map_rec[substr(rw, 2)];
|
||||
}
|
||||
GenericMap.setupLayer = func(layer, property) {
|
||||
var l = MAP_LAYERS[layer].new(me, layer); # Layer.new(me, layer);
|
||||
l.display_layer = property; #FIXME: use controller object instead here and this overlaps with update_property
|
||||
#print("Set up layer with toggle property=", property);
|
||||
l._view.setVisible( getprop(property) ) ;
|
||||
append(me.layers, l);
|
||||
return l;
|
||||
}
|
||||
|
||||
if( rws_done[rw_rec] != nil )
|
||||
continue;
|
||||
# features are layers - so this will do layer setup and then register listeners for each layer
|
||||
GenericMap.setupFeature = func(layer, property, init ) {
|
||||
var l=me.setupLayer( layer, property );
|
||||
me.listen(property, func l.toggle() ); #TODO: should use the controller object here !
|
||||
|
||||
var rw_rec = me._apt.runways[rw_rec];
|
||||
if( rw_rec != nil )
|
||||
thresh_rec = rw_rec.threshold;
|
||||
}
|
||||
l._model._update_property=property; #TODO: move somewhere else - this is the property that is mapped to the CHECKBOX
|
||||
l._model._view_handle = l; #FIXME: very crude, set a handle to the view(group), so that the model can notify it (for updates)
|
||||
l._model._map_handle = me; #FIXME: added here so that layers can send update requests to the parent map
|
||||
#print("Setting up layer init for property:", init);
|
||||
|
||||
rws_done[rw] = 1;
|
||||
|
||||
rw = me._apt.runways[rw];
|
||||
|
||||
var clr = SURFACECOLORS[rw.surface];
|
||||
if (clr == nil) { clr = SURFACECOLORS[0]};
|
||||
|
||||
var icon_rw =
|
||||
me.grp_apt.createChild("path", "runway-" ~ rw.id)
|
||||
.setStrokeLineWidth(0.5)
|
||||
.setColor(1.0,1.0,1.0)
|
||||
.setColorFill(clr.r, clr.g, clr.b);
|
||||
|
||||
var rwy = Runway.new(rw);
|
||||
var beg_thr = rwy.pointOffCenterline(rw.threshold);
|
||||
var beg_thr1 = rwy.pointOffCenterline(rw.threshold, 0.5 * rw.width);
|
||||
var beg_thr2 = rwy.pointOffCenterline(rw.threshold, -0.5 * rw.width);
|
||||
var beg1 = rwy.pointOffCenterline(0, 0.5 * rw.width);
|
||||
var beg2 = rwy.pointOffCenterline(0, -0.5 * rw.width);
|
||||
|
||||
var end_thr = rwy.pointOffCenterline(rw.length - thresh_rec);
|
||||
var end_thr1 = rwy.pointOffCenterline(rw.length - thresh_rec, 0.5 * rw.width);
|
||||
var end_thr2 = rwy.pointOffCenterline(rw.length - thresh_rec, -0.5 * rw.width);
|
||||
var end1 = rwy.pointOffCenterline(rw.length, 0.5 * rw.width);
|
||||
var end2 = rwy.pointOffCenterline(rw.length, -0.5 * rw.width);
|
||||
|
||||
icon_rw.setDataGeo
|
||||
(
|
||||
[ canvas.Path.VG_MOVE_TO,
|
||||
canvas.Path.VG_LINE_TO,
|
||||
canvas.Path.VG_LINE_TO,
|
||||
canvas.Path.VG_LINE_TO,
|
||||
canvas.Path.VG_CLOSE_PATH ],
|
||||
[ beg1[0], beg1[1],
|
||||
beg2[0], beg2[1],
|
||||
end2[0], end2[1],
|
||||
end1[0], end1[1] ]
|
||||
);
|
||||
|
||||
if( rw.length / rw.width > 3 and !is_heli )
|
||||
{
|
||||
# only runways which are much longer than wide are
|
||||
# real runways, otherwise it's probably a heliport.
|
||||
var icon_cl =
|
||||
me.grp_apt.createChild("path", "centerline")
|
||||
.setStrokeLineWidth(0.5)
|
||||
.setColor(1,1,1)
|
||||
.setStrokeDashArray([15, 10]);
|
||||
|
||||
icon_cl.setDataGeo
|
||||
(
|
||||
[ canvas.Path.VG_MOVE_TO,
|
||||
canvas.Path.VG_LINE_TO ],
|
||||
[ beg_thr[0], beg_thr[1],
|
||||
end_thr[0], end_thr[1] ]
|
||||
);
|
||||
|
||||
var icon_thr =
|
||||
me.grp_apt.createChild("path", "threshold")
|
||||
.setStrokeLineWidth(1.5)
|
||||
.setColor(1,1,1);
|
||||
|
||||
icon_thr.setDataGeo
|
||||
(
|
||||
[ canvas.Path.VG_MOVE_TO,
|
||||
canvas.Path.VG_LINE_TO,
|
||||
canvas.Path.VG_MOVE_TO,
|
||||
canvas.Path.VG_LINE_TO ],
|
||||
[ beg_thr1[0], beg_thr1[1],
|
||||
beg_thr2[0], beg_thr2[1],
|
||||
end_thr1[0], end_thr1[1],
|
||||
end_thr2[0], end_thr2[1] ]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (me._display_parking)
|
||||
{
|
||||
foreach(var park; me._apt.parking())
|
||||
{
|
||||
var icon_park =
|
||||
me.grp_apt.createChild("text", "parking-" ~ park.name)
|
||||
.setDrawMode( canvas.Text.ALIGNMENT
|
||||
+ canvas.Text.TEXT )
|
||||
.setText(park.name)
|
||||
.setFont("LiberationFonts/LiberationMono-Bold.ttf")
|
||||
.setGeoPosition(park.lat, park.lon)
|
||||
.setFontSize(15, 1.3);
|
||||
}
|
||||
}
|
||||
|
||||
if (me._display_tower)
|
||||
{
|
||||
var icon_tower =
|
||||
me.grp_apt.createChild("path", "tower")
|
||||
.setStrokeLineWidth(1)
|
||||
.setScale(1.5)
|
||||
.setColor(0.2,0.2,1.0)
|
||||
.moveTo(-3, 0)
|
||||
.vert(-10)
|
||||
.line(-3, -10)
|
||||
.horiz(12)
|
||||
.line(-3, 10)
|
||||
.vert(10);
|
||||
|
||||
var pos = me._apt.tower();
|
||||
icon_tower.setGeoPosition(pos.lat, pos.lon);
|
||||
}
|
||||
}
|
||||
l._model._input_property = init; # FIXME: init property = input property - needs to be improved!
|
||||
me.listen(init, func l._model.init() ); #TODO: makes sure that the layer's init method for the MODEL is invoked
|
||||
me; #chainable
|
||||
};
|
||||
|
||||
# This will read in the config and procedurally instantiate all requested layers and link them to toggle properties
|
||||
# FIXME: this is currently GUI specific and doesn't yet support instrument use, i.e. needs to be generalized further
|
||||
GenericMap.pickupFeatures = func(DIALOG_CANVAS) {
|
||||
var dlgroot = DIALOG_CANVAS.getNode("features/dialog-root").getValue();
|
||||
# print("Picking up features for:", DIALOG_CANVAS.getPath() );
|
||||
var layers=DIALOG_CANVAS.getNode("features").getChildren("layer");
|
||||
foreach(var n; layers) {
|
||||
var name = n.getNode("name").getValue();
|
||||
var toggle = n.getNode("property").getValue();
|
||||
var init = n.getNode("init-property").getValue();
|
||||
init = dlgroot ~"/"~init;
|
||||
var property = dlgroot ~"/"~toggle;
|
||||
# print("Adding layer:",n.getNode("name").getValue() );
|
||||
me.setupFeature(name, property, init);
|
||||
}
|
||||
me;
|
||||
}
|
||||
|
||||
# NOT a method, cmdarg() is no longer meaningful when the canvas nasal block is executed
|
||||
# so this needs to be called in the dialog's OPEN block instead - TODO: generalize
|
||||
#FIXME: move somewhere else, this is a GUI helper and should probably be generalized and moved to gui.nas
|
||||
GenericMap.setupGUI = func (dialog, group) {
|
||||
var group = gui.findElementByName(cmdarg() , group);
|
||||
|
||||
var layers=dialog.getNode("features").getChildren("layer");
|
||||
var template = dialog.getNode("checkbox-toggle-template");
|
||||
var dlgroot = dialog.getNode("features/dialog-root").getValue();
|
||||
var zoom = dlgroot ~"/"~ dialog.getNode("features/range-property").getValue();
|
||||
var i=0;
|
||||
foreach(var n; layers) {
|
||||
var name = n.getNode("name").getValue();
|
||||
var toggle = dlgroot ~ "/" ~ n.getNode("property").getValue();
|
||||
var label = n.getNode("description",1).getValue() or name;
|
||||
|
||||
var default = n.getNode("default",1).getValue();
|
||||
default = (default=="enabled")?1:0;
|
||||
#print("Layer default for", name ," is:", default);
|
||||
setprop(toggle, default); # set the checkbox to its default setting
|
||||
|
||||
var hide_checkbox = n.getNode("hide-checkbox",1).getValue();
|
||||
hide_checkbox = (hide_checkbox=="true")?1:0;
|
||||
|
||||
var checkbox = group.getChild("checkbox",i, 1); #FIXME: compute proper offset dynamically, will currently overwrite other existing checkboxes!
|
||||
|
||||
props.copy(template, checkbox);
|
||||
checkbox.getNode("name").setValue("display-"~name);
|
||||
checkbox.getNode("label").setValue(label);
|
||||
checkbox.getNode("property").setValue(toggle);
|
||||
checkbox.getNode("binding/object-name").setValue("display-"~name);
|
||||
checkbox.getNode("enabled",1).setValue(!hide_checkbox);
|
||||
i+=1;
|
||||
}
|
||||
|
||||
#add zoom buttons procedurally:
|
||||
var template = dialog.getNode("zoom-template");
|
||||
template.getNode("button[0]/binding[0]/property[0]").setValue(zoom);
|
||||
template.getNode("text[0]/property[0]").setValue(zoom);
|
||||
template.getNode("button[1]/binding[0]/property[0]").setValue(zoom);
|
||||
template.getNode("button[1]/binding[0]/max[0]").setValue( i );
|
||||
props.copy(template, group);
|
||||
}
|
||||
|
||||
###
|
||||
# TODO: StylableGenericMap (colors, fonts, symbols)
|
||||
#
|
||||
|
||||
var AirportMap = {};
|
||||
AirportMap.new = func(parent,name) make(GenericMap.new(parent,name), AirportMap);
|
||||
#TODO: Use real MVC (DataProvider/PositionedProvider) here
|
||||
|
||||
|
||||
# this is currently "directly" invoked via a listener, needs to be changed
|
||||
# to use the controller object instead
|
||||
# TODO: adopt real MVC here
|
||||
# FIXME: this must currently be explicitly called by the model, we need to use a wrapper to call it automatically instead!
|
||||
LayerModel.notifyView = func () {
|
||||
# print("View notified");
|
||||
me._view_handle.update(); # update the layer/group
|
||||
me._map_handle.updateState(); # update the map
|
||||
}
|
||||
|
||||
# ID
|
||||
var SingleAirportProvider = {};
|
||||
|
||||
# inputs: position, range
|
||||
var MultiAirportProvider = {};
|
||||
|
||||
#TODO: remove and unify with update()
|
||||
AirportMap.init = func {
|
||||
me.resetLayers();
|
||||
me.updateState();
|
||||
}
|
||||
|
||||
# MultiObjectLayer:
|
||||
# - Airports
|
||||
# - Traffic (MP/AI)
|
||||
# - Navaids
|
||||
#
|
||||
|
||||
# TODO: a "MapLayer" is a full MVC implementation that is owned by a "LayeredMap"
|
||||
|
||||
var MAP_LAYERS = {};
|
||||
var register_layer = func(name, layer) MAP_LAYERS[name]=layer;
|
||||
|
||||
var MVC_FOLDER = getprop("/sim/fg-root") ~ "/Nasal/canvas/map/";
|
||||
var load_modules = func(vec) foreach(var file; vec) io.load_nasal(MVC_FOLDER~file, "canvas");
|
||||
|
||||
# TODO: read in the file names dynamically: *.draw, *.model, *.layer
|
||||
|
||||
var DRAWABLES = ["navaid.draw", "parking.draw", "runways.draw", "taxiways.draw", "tower.draw"];
|
||||
load_modules(DRAWABLES);
|
||||
|
||||
var MODELS = ["airports.model", "navaids.model",];
|
||||
load_modules(MODELS);
|
||||
|
||||
var LAYERS = ["runways.layer", "taxiways.layer", "parking.layer", "tower.layer", "navaids.layer","test.layer",];
|
||||
load_modules(LAYERS);
|
||||
|
||||
#TODO: Implement!
|
||||
var CONTROLLERS = [];
|
||||
load_modules(CONTROLLERS);
|
||||
|
|
0
Nasal/canvas/map/README.txt
Normal file
0
Nasal/canvas/map/README.txt
Normal file
27
Nasal/canvas/map/airports.model
Normal file
27
Nasal/canvas/map/airports.model
Normal file
|
@ -0,0 +1,27 @@
|
|||
var AirportModel = {};
|
||||
AirportModel.new = func make(AirportModel, LayerModel);
|
||||
|
||||
# FIXME: Just testing for now: This really shouldn't be part of the core LayerModel, needs to go to "AirportModel" instead
|
||||
# FIXME: This will get called ONCE for EACH layer that uses the AirportModel, so VERY inefficient ATM! => should be shared among layers
|
||||
AirportModel.init = func {
|
||||
# print("AirportModel initialized!");
|
||||
# me._map_handle.resetLayers();
|
||||
me._view_handle.reset();
|
||||
var id = getprop(me._input_property); # HACK: this needs to be handled via the controller - introduce "input_property"
|
||||
#print("ID is:", id);
|
||||
(id == "") and return;
|
||||
var apt=airportinfo(id); # FIXME: replace with controller call to update the model
|
||||
#var airports = findAirportsWithinRange(apt.lat, apt.lon, 10); # HACK: expose the range !!
|
||||
foreach(var a; [ apt ]) #FIXME: move to separate method: "populate"
|
||||
# print("storing:", a.id) and
|
||||
me.push(a);
|
||||
#print("Work items in Model:", me.hasData() );
|
||||
#print("Model updated!!");
|
||||
|
||||
# set RefPos and hdg to apt !!
|
||||
me._map_handle.setRefPos(apt.lat, apt.lon);
|
||||
|
||||
#TODO: Notify view on update - use proper NOTIFICATIONS (INIT; UPDATE etc)
|
||||
me.notifyView();
|
||||
}
|
||||
|
0
Nasal/canvas/map/fixes.model
Normal file
0
Nasal/canvas/map/fixes.model
Normal file
15
Nasal/canvas/map/navaid.draw
Normal file
15
Nasal/canvas/map/navaid.draw
Normal file
|
@ -0,0 +1,15 @@
|
|||
##
|
||||
# FIXME: until we have better instancing support for symbols, it would be better to return a functor here
|
||||
# so that symbols are only parsed once
|
||||
var NAVAID_CACHE = {};
|
||||
var draw_navaid = func (group, navaid, lod) {
|
||||
#var group = group.createChild("group", "navaid");
|
||||
DEBUG and print("Drawing navaid:", navaid.id);
|
||||
var symbols = {NDB:"/gui/dialogs/images/ndb_symbol.svg"}; # TODO: add more navaid symbols here
|
||||
if (symbols[navaid.type] == nil) return print("Missing svg image for navaid:", navaid.type);
|
||||
|
||||
var symbol_navaid = group.createChild("group", "navaid");
|
||||
canvas.parsesvg(symbol_navaid, symbols[navaid.type]);
|
||||
symbol_navaid.setGeoPosition(navaid.lat, navaid.lon);
|
||||
}
|
||||
|
9
Nasal/canvas/map/navaids.layer
Normal file
9
Nasal/canvas/map/navaids.layer
Normal file
|
@ -0,0 +1,9 @@
|
|||
var NavLayer = {};
|
||||
NavLayer.new = func(group,name) {
|
||||
var m=Layer.new(group, name, NavaidModel);
|
||||
m.setDraw (func draw_layer(layer:m, callback: draw_navaid, lod:0) );
|
||||
return m;
|
||||
}
|
||||
|
||||
register_layer("navaids", NavLayer);
|
||||
|
11
Nasal/canvas/map/navaids.model
Normal file
11
Nasal/canvas/map/navaids.model
Normal file
|
@ -0,0 +1,11 @@
|
|||
var NavaidModel = {};
|
||||
NavaidModel.new = func make(LayerModel, NavaidModel);
|
||||
NavaidModel.init = func {
|
||||
me._view_handle.reset();
|
||||
var navaids = findNavaidsWithinRange(15);
|
||||
foreach(var n; navaids)
|
||||
me.push(n);
|
||||
me.notifyView();
|
||||
}
|
||||
|
||||
|
15
Nasal/canvas/map/parking.draw
Normal file
15
Nasal/canvas/map/parking.draw
Normal file
|
@ -0,0 +1,15 @@
|
|||
var draw_parking = func(group, apt, lod) {
|
||||
var group = group.createChild("group", "apt-"~apt.id);
|
||||
foreach(var park; apt.parking())
|
||||
{
|
||||
var icon_park =
|
||||
group.createChild("text", "parking-" ~ park.name)
|
||||
.setDrawMode( canvas.Text.ALIGNMENT
|
||||
+ canvas.Text.TEXT )
|
||||
.setText(park.name)
|
||||
.setFont("LiberationFonts/LiberationMono-Bold.ttf")
|
||||
.setGeoPosition(park.lat, park.lon)
|
||||
.setFontSize(15, 1.3);
|
||||
}
|
||||
}
|
||||
|
9
Nasal/canvas/map/parking.layer
Normal file
9
Nasal/canvas/map/parking.layer
Normal file
|
@ -0,0 +1,9 @@
|
|||
#TODO: use custom Model/DataProvider
|
||||
var ParkingLayer = {}; # make(Layer);
|
||||
ParkingLayer.new = func(group, name) {
|
||||
var m=Layer.new(group, name, AirportModel ); #FIXME: AirportModel can be shared by Taxiways, Runways etc!!
|
||||
m.setDraw( func draw_layer(layer: m, callback: draw_parking, lod:0 ) );
|
||||
return m;
|
||||
}
|
||||
|
||||
register_layer("parkings", ParkingLayer);
|
113
Nasal/canvas/map/runways.draw
Normal file
113
Nasal/canvas/map/runways.draw
Normal file
|
@ -0,0 +1,113 @@
|
|||
|
||||
#TODO: split: draw_single_runway(pos)
|
||||
var draw_runways = func(group, apt,lod) {
|
||||
DEBUG and print("Drawing runways for:", apt.id);
|
||||
# var group = group.createChild("group", "apt-"~apt.id);
|
||||
# group = group.createChild("group", "runways");
|
||||
var rws_done = {};
|
||||
foreach(var rw; keys(apt.runways))
|
||||
{
|
||||
var is_heli = substr(rw, 0, 1) == "H";
|
||||
var rw_dir = is_heli ? nil : int(substr(rw, 0, 2));
|
||||
|
||||
var rw_rec = "";
|
||||
var thresh_rec = 0;
|
||||
if( rw_dir != nil )
|
||||
{
|
||||
rw_rec = sprintf("%02d", math.mod(rw_dir - 18, 36));
|
||||
if( size(rw) == 3 )
|
||||
{
|
||||
var map_rec = {
|
||||
"R": "L",
|
||||
"L": "R",
|
||||
"C": "C"
|
||||
};
|
||||
rw_rec ~= map_rec[substr(rw, 2)];
|
||||
}
|
||||
|
||||
if( rws_done[rw_rec] != nil )
|
||||
continue;
|
||||
|
||||
var rw_rec = apt.runways[rw_rec];
|
||||
if( rw_rec != nil )
|
||||
thresh_rec = rw_rec.threshold;
|
||||
}
|
||||
|
||||
rws_done[rw] = 1;
|
||||
|
||||
rw = apt.runways[rw];
|
||||
|
||||
var clr = SURFACECOLORS[rw.surface];
|
||||
if (clr == nil) { clr = SURFACECOLORS[0]};
|
||||
|
||||
var icon_rw =
|
||||
group.createChild("path", "runway-" ~ rw.id)
|
||||
.setStrokeLineWidth(0.5)
|
||||
.setColor(1.0,1.0,1.0)
|
||||
.setColorFill(clr.r, clr.g, clr.b);
|
||||
|
||||
var rwy = Runway.new(rw);
|
||||
var beg_thr = rwy.pointOffCenterline(rw.threshold);
|
||||
var beg_thr1 = rwy.pointOffCenterline(rw.threshold, 0.5 * rw.width);
|
||||
var beg_thr2 = rwy.pointOffCenterline(rw.threshold, -0.5 * rw.width);
|
||||
var beg1 = rwy.pointOffCenterline(0, 0.5 * rw.width);
|
||||
var beg2 = rwy.pointOffCenterline(0, -0.5 * rw.width);
|
||||
|
||||
var end_thr = rwy.pointOffCenterline(rw.length - thresh_rec);
|
||||
var end_thr1 = rwy.pointOffCenterline(rw.length - thresh_rec, 0.5 * rw.width);
|
||||
var end_thr2 = rwy.pointOffCenterline(rw.length - thresh_rec, -0.5 * rw.width);
|
||||
var end1 = rwy.pointOffCenterline(rw.length, 0.5 * rw.width);
|
||||
var end2 = rwy.pointOffCenterline(rw.length, -0.5 * rw.width);
|
||||
|
||||
icon_rw.setDataGeo
|
||||
(
|
||||
[ canvas.Path.VG_MOVE_TO,
|
||||
canvas.Path.VG_LINE_TO,
|
||||
canvas.Path.VG_LINE_TO,
|
||||
canvas.Path.VG_LINE_TO,
|
||||
canvas.Path.VG_CLOSE_PATH ],
|
||||
[ beg1[0], beg1[1],
|
||||
beg2[0], beg2[1],
|
||||
end2[0], end2[1],
|
||||
end1[0], end1[1] ]
|
||||
);
|
||||
|
||||
if( rw.length / rw.width > 3 and !is_heli )
|
||||
{
|
||||
# only runways which are much longer than wide are
|
||||
# real runways, otherwise it's probably a heliport.
|
||||
var icon_cl =
|
||||
group.createChild("path", "centerline")
|
||||
.setStrokeLineWidth(0.5)
|
||||
.setColor(1,1,1)
|
||||
.setStrokeDashArray([15, 10]);
|
||||
|
||||
icon_cl.setDataGeo
|
||||
(
|
||||
[ canvas.Path.VG_MOVE_TO,
|
||||
canvas.Path.VG_LINE_TO ],
|
||||
[ beg_thr[0], beg_thr[1],
|
||||
end_thr[0], end_thr[1] ]
|
||||
);
|
||||
|
||||
var icon_thr =
|
||||
group.createChild("path", "threshold")
|
||||
.setStrokeLineWidth(1.5)
|
||||
.setColor(1,1,1);
|
||||
|
||||
icon_thr.setDataGeo
|
||||
(
|
||||
[ canvas.Path.VG_MOVE_TO,
|
||||
canvas.Path.VG_LINE_TO,
|
||||
canvas.Path.VG_MOVE_TO,
|
||||
canvas.Path.VG_LINE_TO ],
|
||||
[ beg_thr1[0], beg_thr1[1],
|
||||
beg_thr2[0], beg_thr2[1],
|
||||
end_thr1[0], end_thr1[1],
|
||||
end_thr2[0], end_thr2[1] ]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
10
Nasal/canvas/map/runways.layer
Normal file
10
Nasal/canvas/map/runways.layer
Normal file
|
@ -0,0 +1,10 @@
|
|||
#TODO: use custom Model/DataProvider
|
||||
var RunwayLayer = {}; # make(Layer);
|
||||
RunwayLayer.new = func(group, name) {
|
||||
# print("Setting up new TestLayer");
|
||||
var m=Layer.new(group, name, AirportModel ); #FIXME: AirportModel can be shared by Taxiways, Runways etc!!
|
||||
m.setDraw( func draw_layer(layer: m, callback: draw_runways, lod:0 ) );
|
||||
return m;
|
||||
}
|
||||
register_layer("runways", RunwayLayer);
|
||||
|
40
Nasal/canvas/map/taxiways.draw
Normal file
40
Nasal/canvas/map/taxiways.draw
Normal file
|
@ -0,0 +1,40 @@
|
|||
var draw_taxiways = func(group, apt, lod) { # TODO: the LOD arg isn't stricly needed here,
|
||||
# the layer is a conventional canvas group, so it can access its map
|
||||
# parent and just read the "range" property to do LOD handling
|
||||
group.set("z-index",-100); # HACK: we need to encapsulate this
|
||||
# var group = group.createChild("group", "apt-"~apt.id); #FIXME: we don't need to use two nested groups for each taxiway - performance?
|
||||
# group = group.createChild("group", "taxiways");
|
||||
# print("drawing taxiways for:", apt.id);
|
||||
# Taxiways drawn first so the runways and parking positions end up on top.
|
||||
foreach(var taxi; apt.taxiways)
|
||||
{
|
||||
var clr = SURFACECOLORS[taxi.surface];
|
||||
if (clr == nil) { clr = SURFACECOLORS[0]};
|
||||
|
||||
var icon_taxi =
|
||||
group.createChild("path", "taxi")
|
||||
.setStrokeLineWidth(0)
|
||||
.setColor(clr.r, clr.g, clr.b)
|
||||
.setColorFill(clr.r, clr.g, clr.b);
|
||||
|
||||
var txi = Runway.new(taxi);
|
||||
var beg1 = txi.pointOffCenterline(0, 0.5 * taxi.width);
|
||||
var beg2 = txi.pointOffCenterline(0, -0.5 * taxi.width);
|
||||
var end1 = txi.pointOffCenterline(taxi.length, 0.5 * taxi.width);
|
||||
var end2 = txi.pointOffCenterline(taxi.length, -0.5 * taxi.width);
|
||||
|
||||
icon_taxi.setDataGeo
|
||||
(
|
||||
[ canvas.Path.VG_MOVE_TO,
|
||||
canvas.Path.VG_LINE_TO,
|
||||
canvas.Path.VG_LINE_TO,
|
||||
canvas.Path.VG_LINE_TO,
|
||||
canvas.Path.VG_CLOSE_PATH ],
|
||||
[ beg1[0], beg1[1],
|
||||
beg2[0], beg2[1],
|
||||
end2[0], end2[1],
|
||||
end1[0], end1[1] ]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
11
Nasal/canvas/map/taxiways.layer
Normal file
11
Nasal/canvas/map/taxiways.layer
Normal file
|
@ -0,0 +1,11 @@
|
|||
#TODO: use custom Model/DataProvider
|
||||
var TaxiwayLayer = {}; # make(Layer);
|
||||
TaxiwayLayer.new = func(group, name) {
|
||||
# print("Setting up new TestLayer");
|
||||
var m=Layer.new(group, name, AirportModel ); #FIXME: AirportModel can be shared by Taxiways, Runways etc!!
|
||||
m.setDraw( func draw_layer(layer: m, callback: draw_taxiways, lod:0 ) );
|
||||
return m;
|
||||
}
|
||||
|
||||
register_layer("taxiways", TaxiwayLayer);
|
||||
|
11
Nasal/canvas/map/test.layer
Normal file
11
Nasal/canvas/map/test.layer
Normal file
|
@ -0,0 +1,11 @@
|
|||
|
||||
#TODO: use custom Model/DataProvider
|
||||
var TestLayer = {}; # make(Layer);
|
||||
TestLayer.new = func(group, name) {
|
||||
# print("Setting up new TestLayer");
|
||||
var m=Layer.new(group, name, AirportModel ); #FIXME: AirportModel can be shared by Taxiways, Runways etc!!
|
||||
m.setDraw( func draw_layer(layer: m, callback: MAP_LAYERS["runways"], lod:0 ) );
|
||||
return m;
|
||||
}
|
||||
|
||||
register_layer("airport_test", TestLayer);
|
19
Nasal/canvas/map/tower.draw
Normal file
19
Nasal/canvas/map/tower.draw
Normal file
|
@ -0,0 +1,19 @@
|
|||
var draw_tower = func (group, apt,lod) {
|
||||
var group = group.createChild("group", "tower");
|
||||
# TODO: move to map_elements.nas (tower, runway, parking etc)
|
||||
# i.e.: set_element(group, "tower", "style");
|
||||
var icon_tower =
|
||||
group.createChild("path", "tower")
|
||||
.setStrokeLineWidth(1)
|
||||
.setScale(1.5)
|
||||
.setColor(0.2,0.2,1.0)
|
||||
.moveTo(-3, 0)
|
||||
.vert(-10)
|
||||
.line(-3, -10)
|
||||
.horiz(12)
|
||||
.line(-3, 10)
|
||||
.vert(10);
|
||||
|
||||
icon_tower.setGeoPosition(apt.lat, apt.lon);
|
||||
}
|
||||
|
8
Nasal/canvas/map/tower.layer
Normal file
8
Nasal/canvas/map/tower.layer
Normal file
|
@ -0,0 +1,8 @@
|
|||
var TowerLayer = {};
|
||||
TowerLayer.new = func(group, name) {
|
||||
var m=Layer.new(group, name, AirportModel ); #FIXME: AirportModel can be shared by Taxiways, Runways etc!!
|
||||
m.setDraw( func draw_layer(layer: m, callback: draw_tower, lod:0 ) );
|
||||
return m;
|
||||
}
|
||||
register_layer("towers", TowerLayer);
|
||||
|
0
Nasal/canvas/map/waypoints.model
Normal file
0
Nasal/canvas/map/waypoints.model
Normal file
|
@ -24,13 +24,26 @@
|
|||
<binding>
|
||||
<command>dialog-close</command>
|
||||
</binding>
|
||||
<binding>
|
||||
<command>property-toggle</command>
|
||||
<property>/sim/gui/dialogs/airports/signals/dialog-close</property>
|
||||
</binding>
|
||||
</button>
|
||||
</group>
|
||||
|
||||
<hrule/>
|
||||
|
||||
<nasal>
|
||||
<!-- Generalize all this, turn into helpers and load defaults via XML -->
|
||||
<open><![CDATA[
|
||||
## "prologue" currently required by the canvas-generic-map
|
||||
var dialog_name ="airports"; #TODO: use substr() and cmdarg() to get this dynamically
|
||||
var dialog_property = func(p) return "/sim/gui/dialogs/airports/"~p; #TODO: generalize using cmdarg
|
||||
var DIALOG_CANVAS = gui.findElementByName(cmdarg(), "airport-selection");
|
||||
canvas.GenericMap.setupGUI(DIALOG_CANVAS, "canvas-control"); #TODO: this is not a method!
|
||||
## end of canvas-generic-map prologue
|
||||
|
||||
|
||||
setprop("/sim/gui/dialogs/airports/selected-airport/rwy", "");
|
||||
setprop("/sim/gui/dialogs/airports/selected-airport/parkpos", "");
|
||||
setprop("/sim/gui/dialogs/airports/mode", "search");
|
||||
|
@ -176,12 +189,15 @@
|
|||
setprop("/sim/presets/parkpos", getprop("/sim/gui/dialogs/airports/selected-airport/parkpos"));
|
||||
}
|
||||
}
|
||||
|
||||
update_info();
|
||||
|
||||
]]>
|
||||
</open>
|
||||
<close>
|
||||
fgcommand("clear-metar", var n = props.Node.new({ "path": "/sim/gui/dialogs/airports/selected-airport/metar",
|
||||
"station": airport_id}));
|
||||
fgcommand("clear-metar", var n = props.Node.new({ "path": "/sim/gui/dialogs/airports/selected-airport/metar",
|
||||
"station": airport_id}));
|
||||
# map.cleanup_listeners(); #TODO: We should be setting a signal when closing the dialog, so that cleanup code can be invoked automatically
|
||||
</close>
|
||||
</nasal>
|
||||
|
||||
|
@ -575,8 +591,11 @@
|
|||
<group>
|
||||
<layout>vbox</layout>
|
||||
|
||||
<canvas>
|
||||
<name>map-dialog</name>
|
||||
<!-- Instantiate a generic canvas map and parametrize it via inclusion -->
|
||||
<!-- TODO: use params and aliasing -->
|
||||
<canvas include="/Nasal/canvas/generic-canvas-map.xml">
|
||||
|
||||
<name>airport-selection</name>
|
||||
<valign>fill</valign>
|
||||
<halign>fill</halign>
|
||||
<stretch>true</stretch>
|
||||
|
@ -584,207 +603,77 @@
|
|||
<pref-height>400</pref-height>
|
||||
<view n="0">600</view>
|
||||
<view n="1">400</view>
|
||||
|
||||
<nasal>
|
||||
|
||||
|
||||
<load><![CDATA[
|
||||
var my_canvas = canvas.get(cmdarg());
|
||||
my_canvas.setColorBackground(0.2, 0.5, 0.2, 0.5);
|
||||
|
||||
var root = my_canvas.createGroup();
|
||||
|
||||
var map = root.createChild("map", "map-test")
|
||||
.setTranslation(300, 200);
|
||||
|
||||
var layer_runways = map.createChild("group", "runways");
|
||||
|
||||
var updateMap = func() {
|
||||
var id = getprop("/sim/gui/dialogs/airports/selected-airport/id");
|
||||
|
||||
if (id != "") {
|
||||
var apt = airportinfo(id);
|
||||
|
||||
map.removeAllChildren();
|
||||
layer_runways = map.createChild("group", "runways");
|
||||
|
||||
var display_taxiways = getprop("/sim/gui/dialogs/airports/display-taxiways");
|
||||
var display_parking = getprop("/sim/gui/dialogs/airports/display-parking");
|
||||
var display_tower = getprop("/sim/gui/dialogs/airports/display-tower");
|
||||
|
||||
var airport = canvas.AirportMap.new(apt, 1, display_taxiways, display_parking, display_tower);
|
||||
airport.build(layer_runways);
|
||||
<features>
|
||||
<!-- TODO: use params and aliases to make this shorter -->
|
||||
<!-- TODO: support styling, i.e. image sets/fonts and colors to be used -->
|
||||
<!-- this will set up individual "layers" and map them to boolean "toggle" properties -->
|
||||
<!-- providing an optional "description" tag here allows us to create all checkboxes procedurally -->
|
||||
<dialog-root>/sim/gui/dialogs/airports</dialog-root>
|
||||
<range-property>zoom</range-property>
|
||||
|
||||
map._node.getNode("ref-lat", 1).setDoubleValue(apt.lat);
|
||||
map._node.getNode("ref-lon", 1).setDoubleValue(apt.lon);
|
||||
map._node.getNode("hdg", 1).setDoubleValue(0.0);
|
||||
|
||||
updateZoom();
|
||||
}
|
||||
}
|
||||
|
||||
var ranges = [0.1, 0.25, 0.5, 1, 2.5, 5];
|
||||
|
||||
var updateZoom = func()
|
||||
{
|
||||
var z = getprop("/sim/gui/dialogs/airports/zoom");
|
||||
if( z == nil )
|
||||
z = 0;
|
||||
var zoom = ranges[4 - z];
|
||||
map._node.getNode("range", 1).setDoubleValue(zoom);
|
||||
|
||||
};
|
||||
|
||||
var updateRunwayHighlight = func()
|
||||
{
|
||||
var selected_rwy = getprop("/sim/gui/dialogs/airports/selected-airport/rwy");
|
||||
var selected_apt = getprop("/sim/gui/dialogs/airports/selected-airport/id");
|
||||
|
||||
var is_heli = substr(selected_rwy, 0, 1) == "H";
|
||||
var rw_dir = is_heli ? nil : int(substr(selected_rwy, 0, 2));
|
||||
|
||||
var rw_rec = "";
|
||||
if( rw_dir != nil ) {
|
||||
rw_rec = sprintf("%02d", math.mod(rw_dir - 18, 36));
|
||||
if( size(selected_rwy) == 3 ) {
|
||||
var map_rec = {
|
||||
"R": "L",
|
||||
"L": "R",
|
||||
"C": "C"
|
||||
};
|
||||
rw_rec ~= map_rec[substr(selected_rwy, 2)];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var apt; layer_runways.getChildren()) {
|
||||
if (apt.get("id") == "apt-" ~ selected_apt) {
|
||||
foreach (var rwy; apt.getChildren()) {
|
||||
if ((rwy.get("id") == "runway-" ~ selected_rwy) or
|
||||
(rwy.get("id") == "runway-" ~ rw_rec) )
|
||||
{
|
||||
rwy.setColor(1.0,0.0,0.0);
|
||||
} else {
|
||||
rwy.setColor(1.0,1.0,1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
<!-- These are the ranges available for the map: var ranges = [0.1, 0.25, 0.5, 1, 2.5, 5] -->
|
||||
|
||||
var updateParkingHighlight = func()
|
||||
{
|
||||
var selected_parkpos = getprop("/sim/gui/dialogs/airports/selected-airport/parkpos");
|
||||
var selected_apt = getprop("/sim/gui/dialogs/airports/selected-airport/id");
|
||||
|
||||
foreach (var apt; layer_runways.getChildren()) {
|
||||
if (apt.get("id") == "apt-" ~ selected_apt) {
|
||||
foreach (var rwy; apt.getChildren()) {
|
||||
if (rwy.get("id") == "parking-" ~ selected_parkpos) {
|
||||
rwy.setColor(1.0,0.0,0.0);
|
||||
} else {
|
||||
rwy.setColor(1.0,1.0,1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var listeners = [];
|
||||
|
||||
append(listeners, setlistener("/sim/gui/dialogs/airports/selected-airport/id", updateMap));
|
||||
append(listeners, setlistener("/sim/gui/dialogs/airports/selected-airport/rwy", updateRunwayHighlight));
|
||||
append(listeners, setlistener("/sim/gui/dialogs/airports/selected-airport/parkpos", updateParkingHighlight));
|
||||
append(listeners, setlistener("/sim/gui/dialogs/airports/display-taxiways", updateMap));
|
||||
append(listeners, setlistener("/sim/gui/dialogs/airports/display-parking", updateMap));
|
||||
append(listeners, setlistener("/sim/gui/dialogs/airports/display-tower", updateMap));
|
||||
append(listeners, setlistener("/sim/gui/dialogs/airports/zoom", updateZoom));
|
||||
|
||||
update_info();
|
||||
]]>
|
||||
</load>
|
||||
<close><![CDATA[
|
||||
foreach (var listener, listeners)
|
||||
{
|
||||
removelistener(listener);
|
||||
|
||||
}
|
||||
]]>
|
||||
</close>
|
||||
</nasal>
|
||||
<ranges>
|
||||
<range>0.1</range>
|
||||
<range>0.25</range>
|
||||
<range>0.5</range>
|
||||
<range>1</range>
|
||||
<range>2.5</range>
|
||||
<range>5</range>
|
||||
</ranges>
|
||||
|
||||
<!-- available layers and their toggle property (appended to dialog-root specified above) -->
|
||||
|
||||
<layer>
|
||||
<name>runways</name> <!-- the name of the layer -->
|
||||
<init-property>selected-airport/id</init-property> <!-- the init/input property that re-inits the layer MODEL -->
|
||||
<property>display-runways</property> <!-- the property switch that toggles the layer on/off (show/hide) -->
|
||||
<description>Show Runways</description> <!-- the checkbox label for the property -->
|
||||
<default>enabled</default> <!-- default state -->
|
||||
<hide-checkbox>true</hide-checkbox> <!-- if the checkbox should be shown or hidden -->
|
||||
</layer>
|
||||
<layer>
|
||||
<name>taxiways</name>
|
||||
<init-property>selected-airport/id</init-property>
|
||||
<property>display-taxiways</property>
|
||||
<description>Show Taxiways</description>
|
||||
<default>disabled</default>
|
||||
</layer>
|
||||
|
||||
<layer>
|
||||
<name>parkings</name>
|
||||
<init-property>selected-airport/id</init-property>
|
||||
<property>display-parking</property>
|
||||
<description>Show Parking</description>
|
||||
<default>disabled</default>
|
||||
</layer>
|
||||
|
||||
<layer>
|
||||
<name>towers</name>
|
||||
<init-property>selected-airport/id</init-property>
|
||||
<property>display-tower</property>
|
||||
<description>Show Tower</description>
|
||||
<default>enabled</default>
|
||||
</layer>
|
||||
<!-- Uncomment this to add a navaid layer (not yet fully implemented, and no LOD yet)
|
||||
<layer>
|
||||
<name>navaids</name>
|
||||
<init-property>selected-airport/id</init-property>
|
||||
<property>display-navaids</property>
|
||||
<description>Display Navaids within current range</description>
|
||||
<default>disabled</default>
|
||||
</layer>
|
||||
-->
|
||||
|
||||
</features>
|
||||
</canvas>
|
||||
|
||||
<hrule/>
|
||||
|
||||
<group>
|
||||
<name>canvas-control</name> <!-- this is the handle we use to procedurally add all "toggle layer" checkboxes and the zoom control-->
|
||||
<layout>hbox</layout>
|
||||
|
||||
<button>
|
||||
<name>zoomout</name>
|
||||
<legend>-</legend>
|
||||
<pref-width>22</pref-width>
|
||||
<pref-height>22</pref-height>
|
||||
|
||||
<binding>
|
||||
<command>property-adjust</command>
|
||||
<property>/sim/gui/dialogs/airports/zoom</property>
|
||||
<min>0</min>
|
||||
<step>-1</step>
|
||||
</binding>
|
||||
</button>
|
||||
|
||||
<text>
|
||||
<label>MMMM</label>
|
||||
<halign>center</halign>
|
||||
<format>Zoom %d</format>
|
||||
<property>/sim/gui/dialogs/airports/zoom</property>
|
||||
<live>true</live>
|
||||
</text>
|
||||
|
||||
<button>
|
||||
<name>zoomin</name>
|
||||
<legend>+</legend>
|
||||
<pref-width>22</pref-width>
|
||||
<pref-height>22</pref-height>
|
||||
|
||||
<binding>
|
||||
<command>property-adjust</command>
|
||||
<property>/sim/gui/dialogs/airports/zoom</property>
|
||||
<step>1</step>
|
||||
<max>4</max>
|
||||
</binding>
|
||||
</button>
|
||||
|
||||
<empty><stretch>true</stretch></empty>
|
||||
|
||||
<checkbox>
|
||||
<name>display-taxiways</name>
|
||||
<label>Display Taxiways</label>
|
||||
<property>/sim/gui/dialogs/airports/display-taxiways</property>
|
||||
<binding>
|
||||
<command>dialog-apply</command>
|
||||
<object-name>display-taxiways</object-name>
|
||||
</binding>
|
||||
</checkbox>
|
||||
|
||||
<checkbox>
|
||||
<name>display-parking</name>
|
||||
<label>Display Parking Position</label>
|
||||
<property>/sim/gui/dialogs/airports/display-parking</property>
|
||||
<binding>
|
||||
<command>dialog-apply</command>
|
||||
<object-name>display-parking</object-name>
|
||||
</binding>
|
||||
</checkbox>
|
||||
|
||||
<checkbox>
|
||||
<name>display-tower</name>
|
||||
<label>Display Tower</label>
|
||||
<property>/sim/gui/dialogs/airports/display-tower</property>
|
||||
<binding>
|
||||
<command>dialog-apply</command>
|
||||
<object-name>display-tower</object-name>
|
||||
</binding>
|
||||
</checkbox>
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
|
|
744
gui/dialogs/images/ndb_symbol.svg
Normal file
744
gui/dialogs/images/ndb_symbol.svg
Normal file
|
@ -0,0 +1,744 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://web.resource.org/cc/"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="50"
|
||||
height="50"
|
||||
id="svg2"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="0.45.1"
|
||||
sodipodi:docbase="C:\Documents and Settings\JJB\My Documents"
|
||||
sodipodi:docname="NDB Symbol.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape"
|
||||
version="1.0">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
gridtolerance="10000"
|
||||
guidetolerance="3.1"
|
||||
objecttolerance="10"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="4"
|
||||
inkscape:cx="71.027314"
|
||||
inkscape:cy="17.078887"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g3816"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:window-width="1404"
|
||||
inkscape:window-height="874"
|
||||
inkscape:window-x="28"
|
||||
inkscape:window-y="0"
|
||||
width="50px"
|
||||
height="50px"
|
||||
showgrid="false"
|
||||
inkscape:grid-points="true"
|
||||
gridspacingx="5px"
|
||||
gridspacingy="5px"
|
||||
inkscape:guide-points="false" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<g
|
||||
id="g2842"
|
||||
transform="matrix(0.9655323,-0.2602834,0.2602834,0.9655323,-98.55675,-17.522102)"
|
||||
inkscape:transform-center-y="-186.61497"
|
||||
inkscape:transform-center-x="50.854555" />
|
||||
<g
|
||||
id="g4183"
|
||||
transform="translate(6.3118628,-54.869792)">
|
||||
<g
|
||||
transform="translate(-56.311844,-45.130208)"
|
||||
id="g3816">
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:3;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3586"
|
||||
sodipodi:cx="75"
|
||||
sodipodi:cy="125"
|
||||
sodipodi:rx="5"
|
||||
sodipodi:ry="5"
|
||||
d="M 80 125 A 5 5 0 1 1 70,125 A 5 5 0 1 1 80 125 z" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3588"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,98.125)" />
|
||||
<g
|
||||
id="g3652">
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,88.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3648"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,108.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3650"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
</g>
|
||||
<g
|
||||
id="g3656"
|
||||
transform="matrix(0.8660254,-0.5,0.5,0.8660254,-52.451905,54.246825)">
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3658"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,88.125)" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3660"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,108.125)" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.5,-0.8660254,0.8660254,0.5,-70.753175,127.45191)"
|
||||
id="g3662">
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,88.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3664"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,108.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3666"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
</g>
|
||||
<g
|
||||
id="g3668"
|
||||
transform="matrix(0,-1,1,0,-50,200)">
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3670"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,88.125)" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3672"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,108.125)" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(-0.5,-0.8660254,0.8660254,-0.5,4.2468245,252.45191)"
|
||||
id="g3674">
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,88.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3676"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,108.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3678"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
</g>
|
||||
<g
|
||||
id="g3680"
|
||||
transform="matrix(-0.8660254,-0.5,0.5,-0.8660254,77.451905,270.75318)">
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3682"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,88.125)" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3684"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,108.125)" />
|
||||
</g>
|
||||
<g
|
||||
id="g3690"
|
||||
transform="matrix(0.9063078,-0.4226182,0.4226182,0.9063078,-45.800367,43.407896)">
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,83.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3686"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,113.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3688"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.7071067,-0.7071068,0.7071068,0.7071067,-66.421357,89.644658)"
|
||||
id="g3694">
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3696"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,83.125)" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3698"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,113.125)" />
|
||||
</g>
|
||||
<g
|
||||
id="g3700"
|
||||
transform="matrix(0.4226183,-0.9063078,0.9063078,0.4226183,-69.984843,140.1458)">
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,83.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3702"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,113.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3704"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(8.715577e-2,-0.9961946,0.9961946,8.715577e-2,-56.061019,188.82012)"
|
||||
id="g3706">
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3708"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,83.125)" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3710"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,113.125)" />
|
||||
</g>
|
||||
<g
|
||||
id="g3712"
|
||||
transform="matrix(-0.258819,-0.9659258,0.9659258,-0.258819,-26.329304,229.7968)">
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,83.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3714"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,113.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3716"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(-0.5735763,-0.819152,0.819152,-0.5735763,15.624218,258.13344)"
|
||||
id="g3718">
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3720"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,83.125)" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3722"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,113.125)" />
|
||||
</g>
|
||||
<g
|
||||
id="g3724"
|
||||
transform="matrix(-0.8191519,-0.5735764,0.5735764,-0.8191519,64.739336,270.41222)">
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,83.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3726"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,113.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3728"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(-0.9659257,-0.2588191,0.2588191,-0.9659257,115.09204,265.15215)"
|
||||
id="g3730">
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3732"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,83.125)" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3734"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,113.125)" />
|
||||
</g>
|
||||
<g
|
||||
id="g3736"
|
||||
transform="matrix(-0.9961946,8.715564e-2,-8.715564e-2,-0.9961946,160.60905,242.98766)">
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,83.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3738"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,113.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3740"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
</g>
|
||||
<g
|
||||
id="g3746">
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,118.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3742"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,78.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3744"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
</g>
|
||||
<g
|
||||
id="g3750"
|
||||
transform="matrix(0.9659258,-0.258819,0.258819,0.9659258,-29.796818,23.6707)">
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3752"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,118.125)" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3754"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,78.125)" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.8660254,-0.4999999,0.4999999,0.8660254,-52.451906,54.246822)"
|
||||
id="g3756">
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,118.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3758"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,78.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3760"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
</g>
|
||||
<g
|
||||
id="g3762"
|
||||
transform="matrix(0.7071068,-0.7071067,0.7071067,0.7071068,-66.421359,89.644655)">
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3764"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,118.125)" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3766"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,78.125)" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.5,-0.8660253,0.8660253,0.5,-70.753181,127.4519)"
|
||||
id="g3768">
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,118.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3770"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,78.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3772"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
</g>
|
||||
<g
|
||||
id="g3774"
|
||||
transform="matrix(0.2588191,-0.9659257,0.9659257,0.2588191,-65.152165,165.09205)">
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3776"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,118.125)" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3778"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,78.125)" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(8.5712909e-8,-0.9999999,0.9999999,8.5712909e-8,-50.000013,199.99999)"
|
||||
id="g3780">
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,118.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3782"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,78.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3784"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
</g>
|
||||
<g
|
||||
id="g3786"
|
||||
transform="matrix(-0.2588189,-0.9659258,0.9659258,-0.2588189,-26.329318,229.79681)">
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3788"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,118.125)" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3790"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,78.125)" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(-0.4999999,-0.8660254,0.8660254,-0.4999999,4.2468015,252.4519)"
|
||||
id="g3792">
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,118.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3794"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,78.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3796"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
</g>
|
||||
<g
|
||||
id="g3798"
|
||||
transform="matrix(-0.7071067,-0.7071068,0.7071068,-0.7071067,39.644635,266.42135)">
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3800"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,118.125)" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3802"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,78.125)" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(-0.8660253,-0.5,0.5,-0.8660253,77.451877,270.75317)"
|
||||
id="g3804">
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,118.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3806"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
transform="matrix(0.25,0,0,0.25,63.125,78.125)"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
sodipodi:ry="2.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:cx="47.5"
|
||||
id="path3808"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
</g>
|
||||
<g
|
||||
id="g3810"
|
||||
transform="matrix(-0.9659257,-0.2588191,0.2588191,-0.9659257,115.09202,265.15215)">
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3812"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,118.125)" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3814"
|
||||
sodipodi:cx="47.5"
|
||||
sodipodi:cy="107.5"
|
||||
sodipodi:rx="2.5"
|
||||
sodipodi:ry="2.5"
|
||||
d="M 50 107.5 A 2.5 2.5 0 1 1 45,107.5 A 2.5 2.5 0 1 1 50 107.5 z"
|
||||
transform="matrix(0.25,0,0,0.25,63.125,78.125)" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 35 KiB |
Loading…
Add table
Reference in a new issue