/**
* PolylineControl Class v0.1
* Copyright (c) 2008
* Author: Chris Marx
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This class lets you add a polyline control for digitizing polylines to the GeometryControls framework.
*/
/**
* Constructor for PolylineControl, which is used for creating/digitizing polylines.
* @param {Object} opt_opts
* @param {Object} button_opts
* @param {String} opt_opts.img_up_url The image to display on the button in the up state
* @param {String} opt_opts.img_down_url The image to display on the button in the down state
* @param {String} opt_opts.name The name of the button
* @param {String} opt_opts.tooltip The button's tooltip text
* @param {Object} opt_opts.position Position options
* @param {Array} position.controlPosition The offsets for the control's position
* @param {Object} opt_opts.tooltip Tooltip Options
* @param {Array} tooltip.anchor The offset for postion of tooltip
* @param {String} tooltip.cursor_on The url for a custom css cursor
* @param {String} tooltip.cursor_off The url for a custom css cursor
* @param {Object} titles Titles used for tooltip
* @param {String} titles.img_up_url Url of up image
* @param {String} titles.img_down_url Url of down image
* @param {String} titles.tooltip Text of tooltip
* @param {Function} tooltip.callback Optional callback executed for tooltips
* @param {Object} newGeometryOptions The options used when creating new geometries
* @param {String} newGeometryOptions.strokeColor The stroke (line) color
* @param {Integer} newGeometryOptions.strokeWieght The stroke (line) weight (width)
* @param {Float} newGeometryOptions.strokeOpacity The stroke (line) opacity
* @param {Object} opts
* @param {Boolean} opts.clickable Is geometry clickable
* @param {Boolean} opts.geodesic Is the line geodesic
* @param {Boolean} multiEdit Determines whether one can add multiple geometries at once (rather than having the control turn off after each addition)
* @param {Object} htmlTemplateParams Optional data object for html template
* @param {String} cssId The prefix used for id's in the css
* @param {Function} optionalGeometryListeners Optional function to add additional listeners to geometries
* @param {Boolean} autoSave Determines whether saving geometry data also triggers a post to a DB
* @param {Boolean} executeClassExtensions Should the class extenstions function be executed
*/
function PolylineControl(opt_opts) {
var me = this;
me.type = "polyline";
me.name = me.type + "Control";
me.zuper = null;
me.digitizerShape = null;
me.editLineHandler = null;
me.endLineHandler = null;
me.infoWindowHtml = "";
me.styles = {
standard:{}//TODO
};
/**
* Array used for storage. Remember to check for nulls when exporting data
* Geometries are tied to their index, so entries are cleared, not deleted
* titles/descriptions are stored as [][0,1] with 0,1 entries for current/previous values
*/
me.storage = [/*array of GeometryControls#beans#Geometry*/];
//self documenting object with default settings
me.Options = {
button_opts:{
img_up_url:'http://www.google.com/intl/en_us/mapfiles/ms/t/Blu.png',
img_down_url:'http://www.google.com/intl/en_us/mapfiles/ms/t/Bld.png',
name:'polyline',
tooltip:'Draw a line'
},
position:{
controlPosition:[210,3]
},
tooltip:{
anchor:[-30,-8],
cursor_on:"", //only for overriding default digitizing cursor
cursor_off:"",
/**
* Polyline titles are different from marker or polygon titles, because distance is reported in the tooltip.
* Note: the tooltip function targets the first childNode (0ft || ) and is necessary for proper functioning
*/
titles:{
start: "0ft
Click to start drawing a line",
middle: "
Click to continue drawing a line",
end: "
Click a vertex once, or double click on the map to end this line"
},
/**
* @see GeometryControls#tooltipFactory
* @param {GLatLng} latlng Latlng from mousemove listener
* @param {DOM Element} tooltipContainer The div container used by tooltip
*/
callback:function(latlng,tooltipContainer){
if (me.digitizerShape.getVertexCount() > 0) {
tooltipContainer.childNodes[0].data = me.zuper.convertFromMetric(
me.digitizerShape.getVertex(me.digitizerShape.getVertexCount() - 1).distanceFrom(latlng),
me.digitizerShape.getLength()
);
}
}
},
newGeometryOptions: {
strokeColor:"#0000FF",
strokeWeight:3,
strokeOpacity:0.25,
opts:{
clickable:true,
geodesic:false
}
},
multiEdit:false, //need this for polys?
htmlTemplateParams:{},
cssId:"emmc-polygon",
optionalGeometryListeners:null,
autoSave:false,
executeClassExtensions:true
};
//overide the default marker options
if(typeof(opt_opts)!="undefined"){
for (var o in opt_opts) {
if(typeof(opt_opts[o]) === "object"){
for (var p in opt_opts[o]){
me.polygonOptions[o][p] = opt_opts[o][p];
}
} else {
me.polygonOptions[o] = opt_opts[o];
}
}
} else {
//me._super.debug("??");
}
};
PolylineControl.prototype = new GControl();
/**
* Expected by GControl, sets control position
*/
PolylineControl.prototype.getDefaultPosition = function(){
var me = this;
return me.zuper.getDefaultPosition(me.Options.position);
};
/**
* Extend for polygon specific implementation
* @param {GMap2} map The map that has had this ExtMapTypeControl added to it.
* @return {DOM Object} Div that holds the control
*/
PolylineControl.prototype.initialize = function(map){
var me = this;
me.container = document.createElement("div");
me.container.id = "mymaps-control-"+me.Options.button_opts.name;
var button = me.zuper.createButton({
controlName:me.name,
button_opts:me.Options.button_opts,
startDigitizing:function(){
me.startDigitizing();
},
stopDigitizing:function(t){
me.stopDigitizing(t);
}
});
me.container.appendChild(button.img);
map.getContainer().appendChild(me.container);
me.runInitFunctions();
return me.container;
};
/**
* Remember, all init functions get executed from #initialize because zuper isn't defined
* until the control is added to zuper, with GeometryControls#addControl, and that's
* when initialize is called automatically by GControl
* @see #initialize
*/
PolylineControl.prototype.runInitFunctions = function(){
var me = this;
me.tooltip();
me.assembleInfoWindowHtml(me.Options.htmlTemplateParams);
me.extendGPolyline();
//check for any extended functions
if(me.Options.executeClassExtensions){
me.extendBaseClass();
}
};
/**
* Starts digitizing process, turns on tooltip, calls function for geometry creation
* @see #newGPolyline
* @see #extendBaseClass Important! must annotate AOP extensions, otherwise you will be in pain later on...
*/
PolylineControl.prototype.startDigitizing = function() {
var me = this, zuper = me.zuper, map = zuper.map, Options = me.Options;
me.tooltip.on(Options.tooltip.titles["start"], Options.tooltip.callback);
me.digitizerShape = me.newGPolyline([], Options.newGeometryOptions);
map.addOverlay(me.digitizerShape);
me.digitizerShape.enableDrawing({});
//change the tooltip text while digitizing
//TODO this handler is never cleared, does the GEvent release the memory?
me.editLineHandler = GEvent.addListener(me.digitizerShape,"lineupdated",function(){
switch(me.digitizerShape.getVertexCount()){
case 2: me.tooltip.tooltipContainer.innerHTML = Options.tooltip.titles["middle"];
break;
case 3: me.tooltip.tooltipContainer.innerHTML = Options.tooltip.titles["end"];
break;
}
});
//listen for cancel events
GEvent.addListener(me.digitizerShape,"cancelline",function(){
me.stopDigitizing();
});
//create permanent polyline
me.endLineHandler = GEvent.addListener(me.digitizerShape,"endline",function(latlng){
var coords = [];
for(var i=0;i -1){
var shortName = style.replace("stroke","").toLowerCase();
stroke[shortName] = strokeFlag = styles[style];
}
}
//TODO dont use flag, check if object is empty
if(strokeFlag){
geometry.setStrokeStyle(stroke);
}
//update unsavedStyles
for(var o in styles){
geometry.unsavedStyle[o] = styles[o];
}
}
//style.replace(/\b[a-z]/g,style[0].toUpperCase()); //capitalizes first letter
};
/**
* Loads polylines from json
* @param {Object} record The json representation of polyline
*/
PolylineControl.prototype.loadPolylines = function(record){
var me = this;
var polyline = me.createPolyline(record.coordinates,me.infoWindowHtml,false,record.style);
me.storage[polyline.index].title = [record.title,record.title];
me.storage[polyline.index].description = [record.description,record.description];
me.zuper.map.addOverlay(polyline);
return polyline;
};
/**
* Convenience method to be able to pass in options as an object
* @param {Array} coords An array of GLatLngs
* @param {Object} opts An object with the standard opts for a GPolyline
*/
PolylineControl.prototype.newGPolyline = function(coords, opts){
return new GPolyline(coords, opts.strokeColor, opts.strokeWeight, opts.strokeOpacity, opts.opts);
};
/**
* Convenience add getter/setters for objects that need translation between stored and displayed value
* And do basic input validation (and revert to stored values if values are invalid)
* Note: if these methods are eventually added to the api, then these methods will need to be updated
* to use call() to access super method.
*/
PolylineControl.prototype.extendGPolyline = function(){
GPolyline.unsavedStyle = {};
GPolyline.savedStyle = {};
GPolyline.prototype.getStrokeWeight = function(){
return (this.unsavedStyle.strokeWeight || this.savedStyle.strokeWeight);
};
GPolyline.prototype.setStrokeWeight = function(weight){
if(!isNaN(weight)){
this.unsavedStyle.strokeWeight = (weight > 20) ? 20 : (weight < 1) ? 1 : weight;
} else {
this.unsavedStyle.strokeWeight = this.savedStyle.strokeWeight;
}
return this.unsavedStyle.strokeWeight || this.savedStyle.strokeWeight;
};
GPolyline.prototype.getStrokeOpacity = function(){
return (this.unsavedStyle.strokeOpacity || this.savedStyle.strokeOpacity) * 100;
};
GPolyline.prototype.setStrokeOpacity = function(opacity){
if(!isNaN(opacity)){
var storedOpacity = (opacity > 100) ? 100 : (opacity < 0) ? 0 : opacity;
this.unsavedStyle.strokeOpacity = storedOpacity / 100;
} else {
this.unsavedStyle.strokeOpacity = this.savedStyle.strokeOpacity;
}
return this.unsavedStyle.strokeOpacity || this.savedStyle.strokeOpacity;
};
};
/**
* AOP Extensions to the PolylineControl Class
* Put All Custom Behaviors here, so that you can swap in new versions of the file
* And still have your customizations intact (perhaps they will need tweaking)
*/
PolylineControl.prototype.extendBaseClass = function(){
var me = this, zuper = me.zuper, map = zuper.map;
/**
* Adds a distance readout to the tooltip during digitizing
* @extends startDigitizing
*/
var addEditingDistanceToTooltip = function(){
zuper.aop.addAfter(me, 'addGeometryListeners', function(result){
var polyline = result; //result is the return value from original function
var isTooltipOn = false;
//currently there is no dragstart event. This should be used when it's added (hopefully...)
/*GEvent.addListener(polyline,"dragstart",function(){
me.tooltip.on(convertFromMetric(polyline.getLength()));
});*/
GEvent.addListener(polyline,"drag",function(){
if(!isTooltipOn){
me.tooltip.on();
isTooltipOn = !isTooltipOn;
}
me.tooltip.tooltipContainer.innerHTML = zuper.convertFromMetric(polyline.getLength());
});
GEvent.addListener(polyline,"dragend",function(){
me.tooltip.off();
isTooltipOn = !isTooltipOn;
});
});
}();
};