Beispiel für ein einfaches atlasFX-Tool:
Ein einfaches Tool ist ein Tool, welches lediglich eine visuelle Komponente in der Toolbar definiert und kein eigenes Fenster benötigt. Meist ist diese visuelle Komponente ein Knopf, mit der das Tool aktiviert und deaktiviert wird.
Erweitern der SimpleToolBase-Klasse:
<?xml version="1.0" encoding="utf-8"?>
<tools:SimpleToolBase xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:tools="com.alta4.atlas.tools.*">
</tools:SimpleToolBase>
Die Flex-Komponente, die in der atlasFX-Toolbar angezeigt wird (visuelles Element), wird aus der SimpleToolBar.toolbarVisualElement – Eigenschaft ausgelesen. Diese ist per Voreinstellung this, also die Tool-Klasse selbst.
Hinzufügen des ‚visuellen Elementes‘ für die Toolbar (ein Knopf!):
<fx:Script>
<![CDATA[
[Embed(source='measure20.png')]
private const measureIcon:Class;
[Bindable] private var active:Boolean = false;
protected function button_clickHandler(event:MouseEvent):void {
// TODO Auto-generated method stub
}
]]>
</fx:Script>
<mx:Button
toggle="true" selected="{active}"
icon="{measureIcon}"
width="24" height="24"
buttonMode="true"
toolTip="{resourceManager.getString('tools','measureToolTooltip')}"
click="button_clickHandler(event)"/>
Wenn mehrere visuelle Komponenten der Klasse deklarativ in MXML hinzugefügt werden, muss die toolbarVisualElement-Eigenschaft der SimpleToolBase-Klasse explizit gesetzt werden:
<tools:toolbarVisualElement>
<mx:Button
toggle="true" selected="{active}"
icon="{measureIcon}"
width="24" height="24"
buttonMode="true"
toolTip="{resourceManager.getString('tools','measureToolTooltip')}"
click="button_clickHandler(event)"/>
</tools:toolbarVisualElement>
Beim Erstellen der Tool-Logik stehen jetzt über die SimpleToolBase-Klasse die AtlasMap (Eigenschaft map) sowie an der AtlasMap die Layer (Eigenschaften mapLayers und flatLayerTree) bereit. Wenn die Funktionalität des Tools dies erfordert, kann z.B. ein ESRI GraphicsLayer der AtlasMap hinzugefügt oder Event-Listener an der AtlasMap registriert werden.
Wichtig ist, daß das Tool beim Deaktivieren sämtliche Ressourcen wieder freigibt. D.h. evtl. erstellte GraphicsLayer und Event-Listener müssen wieder entfernt werden, ansonsten droht erratisches Verhalten der Anwendung und ein Memory-Leak.
Beim implementieren der Tool-Logik ist es sinnvoll, alle Funktionalität für das ‚Aufräumen‘ (freigeben von verwendeten Ressource) und deaktivieren des Tools in einer Funktion wie deactivateTool() bereitzustellen.
Wenn ein Tool Funktionalität bereitstellt, welche eine Interaktion mit der Karte erfordert, so sollten folgende Dinge beachtet werden:
<tools:SimpleToolBase xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:tools="com.alta4.atlas.tools.*"
otherToolUsesMapInteraction="otherToolUsesMapInteractionHandler(event)">
<fx:Script>
<![CDATA[
import com.alta4.atlas.tools.ToolEvent;
protected function otherToolUsesMapInteractionHandler(event:ToolEvent):void
{
deactivateTool();
}
Konfigurationseinstellungen eines Tools:
Oftmals ist es wünschenswert, dass ein bestimmter Parameter des Tools vom Benutzer konfigurierbar ist, z.B. benötigt ein Tool zur Streckenmessung eine URL für einen ESRI GeometryServer.
Hierfür wird eine Instanz der AtlasToolConfiguration-Klasse an der Eigenschaft ‚configuration‘ der SimpleToolBase-Klasse instanziiert und dieser dann ein StringConfigurationElement hinzugefügt:
<tools:configuration>
<configuration:AtlasToolConfiguration>
<configuration:StringConfigurationElement id="url"
description="Server-Endpunkt (GeometryServer):"
acceptArcGISServerServiceDragDrop="GeometryServer"
defaultValue="http://MyArcGISServer/arcgis/rest/services/public/Geometry/GeometryServer"/>
</configuration:AtlasToolConfiguration>
</tools:configuration>
Der AtlasToolConfiguration Klasse können so beliebig viele Konfigurations-Elemente hinzugefügt werden. Die Konfiguration eines Elementes ist dann über die id des Konfigurations-Elementes zugänglich.
Im atlasFX Content-Management-System kann dann der Wert nach Aufrufen der Tool-Konfiguration bearbeitet werden:
In diesem Beispiel hat das Konfigurations-Element bereits einen hardgecodeten Wert (defaultValue). Weiterhin kann für jedes Konfigurations-Element im Content-Management-System ein Standardwert vergeben werden, welcher auch im System gespeichert wird. Dieser Wert hat dann eine höhere Priorität als der evtl. vorhandene, hardgecodete Wert. Wird der im Content-Management-System eingestellte Standard-Wert für die Konfiguration gelöscht, so erscheint wieder der hardgecodete Wert, falls vorhanden.
Schliesslich kann beim Einbinden des Tools in eine Karte der Konfigurations-Wert auch kartenbasiert editiert werden, d.h. es kann ein Wert vergeben werden, der nur für eine Karte gilt. Dieser kartenbasierte Wert hat dann eine höhere Priorität als eine Standard-Konfiguration oder einen hardgecodeten Wert.
<?xml version="1.0" encoding="utf-8"?>
<tools:SimpleToolBase
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:tools="com.alta4.atlas.tools.*"
xmlns:configuration="com.alta4.atlas.tools.configuration.*"
layout="absolute"
otherToolUsesMapInteraction=" otherToolUsesMapInteractionHandler(event)"
deactivateTool=" deactivateToolHandler(event)"
>
<fx:Script>
<![CDATA[
import com.alta4.atlas.tools.ToolEvent;
import com.alta4.centralDispatcher.CentralDispatcher;
import com.alta4.icons.Icons;
import com.esri.ags.Graphic;
import com.esri.ags.events.DrawEvent;
import com.esri.ags.geometry.MapPoint;
import com.esri.ags.geometry.Polyline;
import com.esri.ags.layers.GraphicsLayer;
import com.esri.ags.symbols.SimpleLineSymbol;
import com.esri.ags.symbols.TextSymbol;
import com.esri.ags.tasks.GeometryService;
import com.esri.ags.tasks.supportClasses.AreasAndLengthsResult;
import com.esri.ags.tasks.supportClasses.LengthsParameters;
import com.esri.ags.tools.DrawTool;
import mx.controls.Alert;
import mx.rpc.AsyncResponder;
[Embed(source='measure20.png')]
private const measureIcon:Class;
protected function button_clickHandler(event:MouseEvent):void
{
if(active) {
deactivateTool();
} else {
activateTool();
}
}
private var gL:GraphicsLayer;
private var drawTool:DrawTool = new DrawTool();
private var geometryService:GeometryService = new GeometryService();
private var measureLineSymbol:SimpleLineSymbol = new SimpleLineSymbol('solid',0xeb4c0e,1,1);
[Bindable] private var active:Boolean = false;
private function createGraphicsLayer():void {
if(gL && map) {
gL.clear();
map.removeLayer(gL);
}
gL = new GraphicsLayer();
if(map)map.addLayer(gL);
}
private function removeGraphicsLayer():void {
if(gL && map) {
gL.clear();
map.removeLayer(gL);
}
gL = null;
}
private function activateTool():void {
CentralDispatcher.instance.dispatchEvent(new ToolEvent(ToolEvent.TOOL_ACTIVATED,this));
CentralDispatcher.instance.dispatchEvent(new ToolEvent(ToolEvent.TOOL_USES_MAP_INTERACTION,this));
createGraphicsLayer();
drawTool.map = map;
drawTool.showDrawTips = false;
drawTool.graphicsLayer = gL;
drawTool.lineSymbol = measureLineSymbol;
geometryService.url = url.value;
//geometryService.concurrency = "last";
geometryService.showBusyCursor = false;
drawTool.addEventListener(DrawEvent.DRAW_START,drawStart);
drawTool.addEventListener(DrawEvent.DRAW_END,drawEnd);
// activate tool
active = true;
drawTool.activate(DrawTool.POLYLINE);
addMapMouseListener();
}
private function deactivateTool():void {
active = false;
drawTool.removeEventListener(DrawEvent.DRAW_START,drawStart);
drawTool.removeEventListener(DrawEvent.DRAW_END,drawEnd);
if(map)map.openHandCursorVisible = true;
drawTool.deactivate();
removeGraphicsLayer();
removeMapMouseListener();
if(_cursorId)cursorManager.removeCursor(_cursorId);
}
private function addMapMouseListener():void {
if(map) {
map.addEventListener(MouseEvent.ROLL_OVER,setCursor);
map.addEventListener(MouseEvent.ROLL_OUT,removeCursor);
}
}
private function removeMapMouseListener():void {
if(map) {
map.removeEventListener(MouseEvent.ROLL_OVER,setCursor);
map.removeEventListener(MouseEvent.ROLL_OUT,removeCursor);
map.removeEventListener(MouseEvent.CLICK,mapClickWhileToolActive);
}
}
private function drawStart(e:DrawEvent):void {
if(gL) {
drawActive = true;
gL.clear();
if(active) {
var mp:MapPoint = map.toMap(new Point(e.graphic.mouseX,e.graphic.mouseY));
textGraphic('0',mp);
if(gL)map.addEventListener(MouseEvent.CLICK,mapClickWhileToolActive,false,1,true); }
} else {
deactivateTool();
}
}
private var drawActive:Boolean = false;
private function mapClickWhileToolActive(e:MouseEvent):void {
if(!drawActive)return;
_currentMP = map.toMap(new Point(e.stageX,e.stageY));
var gr:Graphic;
for each(var g:Graphic in gL.graphicProvider) {
if(g.geometry is Polyline)gr = g;
}
if(gr && (gr.geometry as Polyline).paths[0].length>2) {
var mp:MapPoint = _currentMP;
var returnFunction2:Function = function(e:Object,o:Object=null):void {
if(e is Array && (e as Array).length) {
if(mp) {
var ts:TextSymbol = makeTextSymbol();
ts.text = formatDistance(e[0]);
if(mp.spatialReference.wkid==102100)ts.text = formatDistance(e[0],true);
var gr:Graphic = makeGraphic();
gr.geometry = mp;
gr.symbol = ts;
if(gL)gL.add(gr);
}
}
}
var lp:LengthsParameters = makeLengthsParameters();
lp.geodesic = true;
lp.polylines = [gr.geometry];
var responder2:AsyncResponder = new AsyncResponder(returnFunction2,faultFunction);
geometryService.lengths(lp,responder2);
}
}
private var _currentMP:MapPoint;
private function drawEnd(e:DrawEvent):void {
drawActive = false;
map.removeEventListener(MouseEvent.CLICK,mapClickWhileToolActive);
}
private function textGraphic(text:String,mp:MapPoint,area:Boolean=false):void {
var ts:TextSymbol = makeTextSymbol();
ts.text = text;
if(area)ts.yoffset=12;
var gr:Graphic = new Graphic(mp,ts);
if(gL) {
gL.add(gr);
}
}
private function formatDistance(meters:Number,webmercator:Boolean=false):String {
if(meters<1000) {
return meters.toFixed(0)+' m';
}
var km:Number = meters / 1000;
var s:String = km.toFixed(3) + " km";
if(resourceManager.localeChain[0]=='de_DE')s = s.replace('.',',');
return s;
}
private function setCursor(e:MouseEvent):void{
if(active && map){
if(_cursorId)cursorManager.removeCursor(_cursorId);
_cursorId = cursorManager.setCursor(Icons.crosshairCursor,1,-8,-8);
}
}
private var _cursorId:int;
private function removeCursor(e:MouseEvent):void{
if(_cursorId)cursorManager.removeCursor(_cursorId);
}
private function faultFunction(o:Object,token:Object=null):void {
Alert.show('fault: '+o.toString());
}
private function makeLengthsParameters():LengthsParameters {
return new LengthsParameters();
}
private function makeGraphic():Graphic {
return new Graphic();
}
private function makeTextSymbol():TextSymbol {
var ts:TextSymbol = new TextSymbol();
var tf:TextFormat = new TextFormat();
tf.bold = true;
tf.color = 0x000000;
tf.font = 'Arial';
tf.size = 14;
ts.textFormat = tf;
ts.yoffset = 15;
return ts;
}
protected function otherToolUsesMapInteractionHandler(event:Event):void
{
deactivateTool();
}
protected function deactivateToolHandler(event:Event):void
{
deactivateTool();
}
]]>
</fx:Script>
<mx:Button id="line"
toggle="true" selected="{active}"
icon="{measureIcon}"
width="24" height="24"
buttonMode="true"
toolTip="{resourceManager.getString('tools','measureToolTooltip')}"
click="button_clickHandler(event)"/>
<tools:configuration>
<configuration:AtlasToolConfiguration>
<configuration:StringConfigurationElement id="url"
description="Server-Endpunkt (GeometryServer):"
acceptArcGISServerServiceDragDrop="GeometryServer"
defaultValue="http://MyArcGISServer/arcgis/rest/services/public/Geometry/GeometryServer"
/>
</configuration:AtlasToolConfiguration>
</tools:configuration>
</tools:SimpleToolBase>
UML-Schema für ein Tool basierend auf SimpleToolBase: