Creating custom position control for GMap
Updated: Jun 3, 2007
Views: 16583
Description: In this tutorial we are going to learn how to create custom controls for GMap. You can also download a fully functional position control form the sources.
| Download source files |
This tutorial uses advanced Action Script techniques. If you're not familliar with OOP, the information provided may be difficult to understand. Anyway please feel free to download the sources and use the control in your projects.
In this tutorial we are going to create a "Map Position" control using subclassing.
| |
All the controls that are included in the GMap, along with custom AFC controls should extend native GControl class. It provides basic functionality which includes:
More information is provided in the GControl.as file's header which can be found in the sources package for this tutorial.
Preparation steps:
After preparation has been completed, we can start developing our custom control. First, let's create a new Action Script file. Select File -> New, then choose "ActionScript file" line and click "OK" button at the bottom of the window.
New and empty ActionScript file will be created named Script-1, click File -> Save and save the file in the same folder where you .fla file is. Name the file "customControl" (the name is case-sensitive!).
Add this AS to the file:
class customControl extends GControl
{
// class constructor (1)
function customControl(initObject:Object)
{
// call super-class contructor, pass initialization object to it (2)
super(initObject);
}
}
{
// class constructor (1)
function customControl(initObject:Object)
{
// call super-class contructor, pass initialization object to it (2)
super(initObject);
}
}
We have created a customControl class that extends GControl class. We have also added a class constructor function (1). The first thing to do in this function is to call super-class constructor passing it an initialization object (2).
The GControl class loads parameters from initialization object. For undefined properties it loads default values. Each control in GMap has it's own default placement, this way when you add controls to the map, they don't overlap each other. We also need to give our control a default placement. Let's align our control bottom-right corner with 10 horizontal and 10 vertical padding.
// define default component alignment and padding
if (!initObject.hasOwnProperty("align") || initObject == undefined) align = "bottom-right";
if (!initObject.hasOwnProperty("padding") || initObject == undefined) padding = {x:10, y:10};
if (!initObject.hasOwnProperty("align") || initObject == undefined) align = "bottom-right";
if (!initObject.hasOwnProperty("padding") || initObject == undefined) padding = {x:10, y:10};
For each propery, we check if it is defined in initialization object and also we check if this object have been passed to the constructor. If not, then we set the default value. Otherwise the values will be loaded in the super-class constructor.
After a new movie clip has been created by a GUI manager, the control has been registered, the method init() will be invoked. We need to define it, to create all the control's child elements. In our case we should draw control's background and create 2 text fields, one for latitude and one for longitude.
function init()
{
// define line thickness and dimensions of the control
var line_width:Number = 1;
var width:Number = 100;
var height:Number = 50;
// create all needed mc's that will be used inside our control
// _mc - it is the root movie clip for the control
_bg_mc = _mc.createEmptyMovieClip("bg_mc", _mc.getNextHighestDepth());
_lat_txt = _mc.createTextField("lat_txt", _mc.getNextHighestDepth(), 5, 5, width - 10, 20);
_lng_txt = _mc.createTextField("lng_txt", _mc.getNextHighestDepth(), 5, 25, width - 10, 20);
}
{
// define line thickness and dimensions of the control
var line_width:Number = 1;
var width:Number = 100;
var height:Number = 50;
// create all needed mc's that will be used inside our control
// _mc - it is the root movie clip for the control
_bg_mc = _mc.createEmptyMovieClip("bg_mc", _mc.getNextHighestDepth());
_lat_txt = _mc.createTextField("lat_txt", _mc.getNextHighestDepth(), 5, 5, width - 10, 20);
_lng_txt = _mc.createTextField("lng_txt", _mc.getNextHighestDepth(), 5, 25, width - 10, 20);
}
As you have noticed we have added 3 variables: line_width, width and height. We did it, so the code will be easier to modify. For example you can change the size of the control just adjusting width and height variables.
Next, let's draw our control's background. We will draw the control in Google-style: black border, white background and a grey shading.
// draw rectangle on the background
with(_bg_mc)
{
// draw back
lineStyle(line_width, 0x0, 100, false, "normal", "square", "miter", 0);
beginFill(0xffffff, 100);
moveTo(0, 0);
lineTo(width, 0);
lineTo(width, height);
lineTo(0, height);
lineTo(0, 0);
endFill();
// draw shading
lineStyle(line_width, 0xCCCCCC, 100, false, "normal", "square", "miter", 0);
moveTo(line_width, height - line_width);
lineTo(width - line_width, height - line_width);
lineTo(width - line_width, line_width);
}
with(_bg_mc)
{
// draw back
lineStyle(line_width, 0x0, 100, false, "normal", "square", "miter", 0);
beginFill(0xffffff, 100);
moveTo(0, 0);
lineTo(width, 0);
lineTo(width, height);
lineTo(0, height);
lineTo(0, 0);
endFill();
// draw shading
lineStyle(line_width, 0xCCCCCC, 100, false, "normal", "square", "miter", 0);
moveTo(line_width, height - line_width);
lineTo(width - line_width, height - line_width);
lineTo(width - line_width, line_width);
}
Now, let's hadle our text fields. We can apply formatting to the text, so that it will look nice. Please note, that we can't apply text formatting if our text field is empty. To avoid this, we set the default text.
// set default text
_lat_txt.text = "LAT: 0.0000";
_lng_txt.text = "LNG: 0.0000";
// text fields' style
_lat_txt.selectable = false;
_lng_txt.selectable = false;
// create new text format
var fmt = new TextFormat();
fmt.font = "_sans";
fmt.bold = true;
// apply formating to first 4 letters
_lat_txt.setTextFormat(0, 4, fmt);
_lng_txt.setTextFormat(0, 4, fmt);
// apply format to numbers
fmt.bold = false;
_lat_txt.setTextFormat(4, 10, fmt);
_lng_txt.setTextFormat(4, 10, fmt);
_lat_txt.text = "LAT: 0.0000";
_lng_txt.text = "LNG: 0.0000";
// text fields' style
_lat_txt.selectable = false;
_lng_txt.selectable = false;
// create new text format
var fmt = new TextFormat();
fmt.font = "_sans";
fmt.bold = true;
// apply formating to first 4 letters
_lat_txt.setTextFormat(0, 4, fmt);
_lng_txt.setTextFormat(0, 4, fmt);
// apply format to numbers
fmt.bold = false;
_lat_txt.setTextFormat(4, 10, fmt);
_lng_txt.setTextFormat(4, 10, fmt);
Basically we can test our control now. First save your class file, then go to the main .fla file you have created. Open actions panel and add this AS for the first frame:
// add our custom control to the map
var myControl:customControl = new customControl();
gMap.addControl(myControl);
var myControl:customControl = new customControl();
gMap.addControl(myControl);
In tnis code we simple create new control object and add it to the map. Now press CTRL+Enter or choose Control -> Test Movie. You should see our control in the bottom-right corner of the map with the text LAT: 0.0000 and LNG: 0.0000.
If everything is alright, let us continue, otherwise correct the errors. You may have noticed that our control is "dead" at the moments: the text doesn't change while you drag the map. To fix that we need to catch MAP_POSITION_CHANGED event and update the text. To access main GMap object we use _owner.core reference.
We add this AS to init() function:
// add event listener to catch MAP_POSITION_CHANGED event
_owner.core.addEventListener("MAP_POSITION_CHANGED", this);
_owner.core.addEventListener("MAP_POSITION_CHANGED", this);
We also write an event handler function inside the class:
public function MAP_POSITION_CHANGED(evt:Object)
{
// set new text for text fields
// note that lat and lng is taken from the event object
_lat_txt.text = "LAT: " + formatNumber(evt.newLat, 4);
_lng_txt.text = "LNG: " + formatNumber(evt.newLng, 4);
// create new text format
var fmt = new TextFormat();
fmt.font = "_sans";
fmt.bold = true;
// apply formating to first 4 letters
_lat_txt.setTextFormat(0, 4, fmt);
_lng_txt.setTextFormat(0, 4, fmt);
// apply format to numbers
fmt.bold = false;
_lat_txt.setTextFormat(4, 10, fmt);
_lng_txt.setTextFormat(4, 10, fmt);
}
{
// set new text for text fields
// note that lat and lng is taken from the event object
_lat_txt.text = "LAT: " + formatNumber(evt.newLat, 4);
_lng_txt.text = "LNG: " + formatNumber(evt.newLng, 4);
// create new text format
var fmt = new TextFormat();
fmt.font = "_sans";
fmt.bold = true;
// apply formating to first 4 letters
_lat_txt.setTextFormat(0, 4, fmt);
_lng_txt.setTextFormat(0, 4, fmt);
// apply format to numbers
fmt.bold = false;
_lat_txt.setTextFormat(4, 10, fmt);
_lng_txt.setTextFormat(4, 10, fmt);
}
We set new text for the text fields and apply the same formatting as we did during the initialization. One function is still missing: formatNumber(). This function rounds and formats latitude and longitude to a specified number of decimal places. We are not going to explain the code for it, just add it's AS to your class:
private function formatNumber(num:Number, dec:Number):String
{
var str:String = String(num);
if (str.indexOf(".") < 0) str += ".";
dec = dec < 0 ? dec-3 : dec-(str.length-str.indexOf(".")-2);
var numbers:Array = str.split("");
while ((dec--)>0) { numbers.push(0); }
while ((dec++)<0) { numbers.splice(numbers.length-1, 1); }
if(numbers[numbers.length-1]==".") numbers.splice(numbers.length-1, 1);
return numbers.join("");
}
{
var str:String = String(num);
if (str.indexOf(".") < 0) str += ".";
dec = dec < 0 ? dec-3 : dec-(str.length-str.indexOf(".")-2);
var numbers:Array = str.split("");
while ((dec--)>0) { numbers.push(0); }
while ((dec++)<0) { numbers.splice(numbers.length-1, 1); }
if(numbers[numbers.length-1]==".") numbers.splice(numbers.length-1, 1);
return numbers.join("");
}
Finally, test the movie.
