Bit stuck on this one. I am retrieving a list of geo coords via JSON and popping them onto a google map. All is working well except in the instance when I have two or more markers on the exact same spot. The API only displays 1 marker - the top one. This is fair enough I suppose but would like to find a way to display them all somehow.
I've searched google and found a few solutions but they mostly seem to be for V2 of the API or just not that great. Ideally I'd like a solution where you click some sort of group marker and that then shows the markers clustered around the spot they are all in.
Anybody had this problem or similar and would care to share a solution?
Take a look at OverlappingMarkerSpiderfier.
There's a demo page, but they don't show markers which are exactly on the same spot, only some which are very close together.
But a real life example with markers on the exact same spot can be seen on http://www.ejw.de/ejw-vor-ort/ (scroll down for the map and click on a few markers to see the spider-effect).
That seems to be the perfect solution for your problem.
Offsetting the markers isn't a real solution if they're located in the same building. What you might want to do is modify the markerclusterer.js like so:
Add a prototype click method in the MarkerClusterer class, like so - we will override this later in the map initialize() function:
MarkerClusterer.prototype.onClick = function() {
return true;
};
In the ClusterIcon class, add the following code AFTER the clusterclick trigger:
// Trigger the clusterclick event.
google.maps.event.trigger(markerClusterer, 'clusterclick', this.cluster_);
var zoom = this.map_.getZoom();
var maxZoom = markerClusterer.getMaxZoom();
// if we have reached the maxZoom and there is more than 1 marker in this cluster
// use our onClick method to popup a list of options
if (zoom >= maxZoom && this.cluster_.markers_.length > 1) {
return markerClusterer.onClickZoom(this);
}
Then, in your initialize() function where you initialize the map and declare your MarkerClusterer object:
markerCluster = new MarkerClusterer(map, markers);
// onClickZoom OVERRIDE
markerCluster.onClickZoom = function() { return multiChoice(markerCluster); }
Where multiChoice() is YOUR (yet to be written) function to popup an InfoWindow with a list of options to select from. Note that the markerClusterer object is passed to your function, because you will need this to determine how many markers there are in that cluster. For example:
function multiChoice(mc) {
var cluster = mc.clusters_;
// if more than 1 point shares the same lat/long
// the size of the cluster array will be 1 AND
// the number of markers in the cluster will be > 1
// REMEMBER: maxZoom was already reached and we can't zoom in anymore
if (cluster.length == 1 && cluster[0].markers_.length > 1)
{
var markers = cluster[0].markers_;
for (var i=0; i < markers.length; i++)
{
// you'll probably want to generate your list of options here...
}
return false;
}
return true;
}
I used this alongside jQuery and it does the job:
var map;
var markers = [];
var infoWindow;
function initialize() {
var center = new google.maps.LatLng(-29.6833300, 152.9333300);
var mapOptions = {
zoom: 5,
center: center,
panControl: false,
zoomControl: false,
mapTypeControl: false,
scaleControl: false,
streetViewControl: false,
overviewMapControl: false,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
$.getJSON('jsonbackend.php', function(data) {
infoWindow = new google.maps.InfoWindow();
$.each(data, function(key, val) {
if(val['LATITUDE']!='' && val['LONGITUDE']!='')
{
// Set the coordonates of the new point
var latLng = new google.maps.LatLng(val['LATITUDE'],val['LONGITUDE']);
//Check Markers array for duplicate position and offset a little
if(markers.length != 0) {
for (i=0; i < markers.length; i++) {
var existingMarker = markers[i];
var pos = existingMarker.getPosition();
if (latLng.equals(pos)) {
var a = 360.0 / markers.length;
var newLat = pos.lat() + -.00004 * Math.cos((+a*i) / 180 * Math.PI); //x
var newLng = pos.lng() + -.00004 * Math.sin((+a*i) / 180 * Math.PI); //Y
var latLng = new google.maps.LatLng(newLat,newLng);
}
}
}
// Initialize the new marker
var marker = new google.maps.Marker({map: map, position: latLng, title: val['TITLE']});
// The HTML that is shown in the window of each item (when the icon it's clicked)
var html = "<div id='iwcontent'><h3>"+val['TITLE']+"</h3>"+
"<strong>Address: </strong>"+val['ADDRESS']+", "+val['SUBURB']+", "+val['STATE']+", "+val['POSTCODE']+"<br>"+
"</div>";
// Binds the infoWindow to the point
bindInfoWindow(marker, map, infoWindow, html);
// Add the marker to the array
markers.push(marker);
}
});
// Make a cluster with the markers from the array
var markerCluster = new MarkerClusterer(map, markers, { zoomOnClick: true, maxZoom: 15, gridSize: 20 });
});
}
function markerOpen(markerid) {
map.setZoom(22);
map.panTo(markers[markerid].getPosition());
google.maps.event.trigger(markers[markerid],'click');
switchView('map');
}
google.maps.event.addDomListener(window, 'load', initialize);
Expanding on Chaoley's answer, I implemented a function that, given a list of locations (objects with lng and lat properties) whose coordinates are exactly the same, moves them away from their original location a little bit (modifying objects in place). They then form a nice circle around the center point.
I found that, for my latitude (52deg North), 0.0003 degrees of circle radius work best, and that you have to make up for the difference between latitude and longitude degrees when converted to kilometres. You can find approximate conversions for your latitude here.
var correctLocList = function (loclist) {
var lng_radius = 0.0003, // degrees of longitude separation
lat_to_lng = 111.23 / 71.7, // lat to long proportion in Warsaw
angle = 0.5, // starting angle, in radians
loclen = loclist.length,
step = 2 * Math.PI / loclen,
i,
loc,
lat_radius = lng_radius / lat_to_lng;
for (i = 0; i < loclen; ++i) {
loc = loclist[i];
loc.lng = loc.lng + (Math.cos(angle) * lng_radius);
loc.lat = loc.lat + (Math.sin(angle) * lat_radius);
angle += step;
}
};
#Ignatius most excellent answer, updated to work with v2.0.7 of MarkerClustererPlus.
Add a prototype click method in the MarkerClusterer class, like so - we will override this later in the map initialize() function:
// BEGIN MODIFICATION (around line 715)
MarkerClusterer.prototype.onClick = function() {
return true;
};
// END MODIFICATION
In the ClusterIcon class, add the following code AFTER the click/clusterclick trigger:
// EXISTING CODE (around line 143)
google.maps.event.trigger(mc, "click", cClusterIcon.cluster_);
google.maps.event.trigger(mc, "clusterclick", cClusterIcon.cluster_); // deprecated name
// BEGIN MODIFICATION
var zoom = mc.getMap().getZoom();
// Trying to pull this dynamically made the more zoomed in clusters not render
// when then kind of made this useless. -NNC # BNB
// var maxZoom = mc.getMaxZoom();
var maxZoom = 15;
// if we have reached the maxZoom and there is more than 1 marker in this cluster
// use our onClick method to popup a list of options
if (zoom >= maxZoom && cClusterIcon.cluster_.markers_.length > 1) {
return mc.onClick(cClusterIcon);
}
// END MODIFICATION
Then, in your initialize() function where you initialize the map and declare your MarkerClusterer object:
markerCluster = new MarkerClusterer(map, markers);
// onClick OVERRIDE
markerCluster.onClick = function(clickedClusterIcon) {
return multiChoice(clickedClusterIcon.cluster_);
}
Where multiChoice() is YOUR (yet to be written) function to popup an InfoWindow with a list of options to select from. Note that the markerClusterer object is passed to your function, because you will need this to determine how many markers there are in that cluster. For example:
function multiChoice(clickedCluster) {
if (clickedCluster.getMarkers().length > 1)
{
// var markers = clickedCluster.getMarkers();
// do something creative!
return false;
}
return true;
};
This is more of a stopgap 'quick and dirty' solution similar to the one Matthew Fox suggests, this time using JavaScript.
In JavaScript you can just offset the lat and long of all of your locations by adding a small random offset to both e.g.
myLocation[i].Latitude+ = (Math.random() / 25000)
(I found that dividing by 25000 gives enough separation but doesn't move the marker significantly from the exact location e.g. a specific address)
This makes a reasonably good job of offsetting them from one another, but only after you've zoomed in closely. When zoomed out, it still won't be clear that there are multiple options for the location.
The answers above are more elegant, but I found a quick and dirty way that actually works really really incredibly well. You can see it in action at www.buildinglit.com
All I did was add a random offset to the latitude and longditude to my genxml.php page so it returns slightly different results each time with offset each time the map is created with markers. This sounds like a hack, but in reality you only need the markers to move a slight nudge in a random direction for them to be clickable on the map if they are overlapping. It actually works really well, I would say better than the spider method because who wants to deal with that complexity and have them spring everywhere. You just want to be able to select the marker. Nudging it randomly works perfect.
Here is an example of the while statement iteration node creation in my php_genxml.php
while ($row = #mysql_fetch_assoc($result)){ $offset = rand(0,1000)/10000000;
$offset2 = rand(0, 1000)/10000000;
$node = $dom->createElement("marker");
$newnode = $parnode->appendChild($node);
$newnode->setAttribute("name", $row['name']);
$newnode->setAttribute("address", $row['address']);
$newnode->setAttribute("lat", $row['lat'] + $offset);
$newnode->setAttribute("lng", $row['lng'] + $offset2);
$newnode->setAttribute("distance", $row['distance']);
$newnode->setAttribute("type", $row['type']);
$newnode->setAttribute("date", $row['date']);
$newnode->setAttribute("service", $row['service']);
$newnode->setAttribute("cost", $row['cost']);
$newnode->setAttribute("company", $company);
Notice under lat and long there is the +offset. from the 2 variables above. I had to divide random by 0,1000 by 10000000 in order to get a decimal that was randomly small enough to just barely move the markers around. Feel free to tinker with that variable to get one that is more precise for your needs.
I like simple solutions so here's mine.
Instead of modifying the lib, which would make it harder to mantain. you can simply watch the event like this
google.maps.event.addListener(mc, "clusterclick", onClusterClick);
then you can manage it on
function onClusterClick(cluster){
var ms = cluster.getMarkers();
i, ie, used bootstrap to show a panel with a list. which i find much more confortable and usable than spiderfying on "crowded" places. (if you are using a clusterer chances are you will end up with collisions once you spiderfy).
you can check the zoom there too.
btw. i just found leaflet and it seems to work much better, the cluster AND spiderfy works very fluidly http://leaflet.github.io/Leaflet.markercluster/example/marker-clustering-realworld.10000.html
and it's open-source.
For situations where there are multiple services in the same building you could offset the markers just a little, (say by .001 degree), in a radius from the actual point. This should also produce a nice visual effect.
Check out Marker Clusterer for V3 - this library clusters nearby points into a group marker. The map zooms in when the clusters are clicked. I'd imagine when zoomed right in you'd still have the same problem with markers on the same spot though.
Updated to work with MarkerClustererPlus.
google.maps.event.trigger(mc, "click", cClusterIcon.cluster_);
google.maps.event.trigger(mc, "clusterclick", cClusterIcon.cluster_); // deprecated name
// BEGIN MODIFICATION
var zoom = mc.getMap().getZoom();
// Trying to pull this dynamically made the more zoomed in clusters not render
// when then kind of made this useless. -NNC # BNB
// var maxZoom = mc.getMaxZoom();
var maxZoom = 15;
// if we have reached the maxZoom and there is more than 1 marker in this cluster
// use our onClick method to popup a list of options
if (zoom >= maxZoom && cClusterIcon.cluster_.markers_.length > 1) {
var markers = cClusterIcon.cluster_.markers_;
var a = 360.0 / markers.length;
for (var i=0; i < markers.length; i++)
{
var pos = markers[i].getPosition();
var newLat = pos.lat() + -.00004 * Math.cos((+a*i) / 180 * Math.PI); // x
var newLng = pos.lng() + -.00004 * Math.sin((+a*i) / 180 * Math.PI); // Y
var finalLatLng = new google.maps.LatLng(newLat,newLng);
markers[i].setPosition(finalLatLng);
markers[i].setMap(cClusterIcon.cluster_.map_);
}
cClusterIcon.hide();
return ;
}
// END MODIFICATION
I used markerclustererplus, and for me this works:
//Code
google.maps.event.addListener(cMarkerClusterer, "clusterclick", function (c) {
var markers = c.getMarkers();
//Check Markers array for duplicate position and offset a little
if (markers .length > 1) {
//Check if all markers are in the same position (with 4 significant digits)
if (markers .every((val, index, arr) => (val.getPosition().lat().toFixed(4) == arr[0].getPosition().lat().toFixed(4)) && (val.getPosition().lng().toFixed(4) == arr[0].getPosition().lng().toFixed(4)))) { /
//Don't modify first element
for (i = 1; i < markers.length; i++) {
var existingMarker = markers[i];
var pos = existingMarker.getPosition();
var quot = 360.0 / markers.length;
var newLat = pos.lat() + -.00008 * Math.cos(+quot * i); //+ -.00008 * Math.cos((+quot * i) / 180 * Math.PI); //x
var newLng = pos.lng() + -.00008 * Math.sin(+quot * i); //+ -.0008 * Math.sin((+quot * i) / 180 * Math.PI); //Y
existingMarker.setPosition(new google.maps.LatLng(newLat, newLng));
}
let cZoom = map.getZoom();
map.setZoom(cZoom-1);
map.setZoom(cZoom+1);
}
}
});
Check this: https://github.com/plank/MarkerClusterer
This is the MarkerCluster modified to have a infoWindow in a cluster marker, when you have several markers in the same position.
You can see how it works here: http://culturedays.ca/en/2013-activities
Giving offset will make the markers faraway when the user zoom in to max. So i found a way to achieve that. this may not be a proper way but it worked very well.
// This code is in swift
for loop markers
{
//create marker
let mapMarker = GMSMarker()
mapMarker.groundAnchor = CGPosition(0.5, 0.5)
mapMarker.position = //set the CLLocation
//instead of setting marker.icon set the iconView
let image:UIIMage = UIIMage:init(named:"filename")
let imageView:UIImageView = UIImageView.init(frame:rect(0,0, ((image.width/2 * markerIndex) + image.width), image.height))
imageView.contentMode = .Right
imageView.image = image
mapMarker.iconView = imageView
mapMarker.map = mapView
}
set the zIndex of the marker so that you will see the marker icon which you want to see on top, otherwise it will animate the markers like auto swapping. when the user tap the marker handle the zIndex to bring the marker on top using zIndex Swap.
How to get away with it..
[Swift]
var clusterArray = [String]()
var pinOffSet : Double = 0
var pinLat = yourLat
var pinLong = yourLong
var location = pinLat + pinLong
A new marker is about to be created? check clusterArray and manipulate it's offset
if(!clusterArray.contains(location)){
clusterArray.append(location)
} else {
pinOffSet += 1
let offWithIt = 0.00025 // reasonable offset with zoomLvl(14-16)
switch pinOffSet {
case 1 : pinLong = pinLong + offWithIt ; pinLat = pinLat + offWithIt
case 2 : pinLong = pinLong + offWithIt ; pinLat = pinLat - offWithIt
case 3 : pinLong = pinLong - offWithIt ; pinLat = pinLat - offWithIt
case 4 : pinLong = pinLong - offWithIt ; pinLat = pinLat + offWithIt
default : print(1)
}
}
result
Adding to Matthew Fox's sneaky genius answer, I have added a small random offset to each lat and lng when setting the marker object. For example:
new LatLng(getLat()+getMarkerOffset(), getLng()+getMarkerOffset()),
private static double getMarkerOffset(){
//add tiny random offset to keep markers from dropping on top of themselves
double offset =Math.random()/4000;
boolean isEven = ((int)(offset *400000)) %2 ==0;
if (isEven) return offset;
else return -offset;
}
I used this http://leaflet.github.io/Leaflet.markercluster/ and perfectly works for me. added full solution.
<html lang="en">
<head>
<script src="https://code.jquery.com/jquery-3.2.1.js" integrity="sha256-DZAnKJ/6XZ9si04Hgrsxu/8s717jcIzLy3oi35EouyE=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.3/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.0.4/leaflet.markercluster.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.3/leaflet.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.0.4/MarkerCluster.Default.css" />
</head>
<body>
<div id="map"></div>
<script>
var addressData = [
{id: 9, name: "Ankita", title: "Manager", latitude: "33.1128019", longitude: "-96.6958939"},
{id: 1, name: "Aarti", title: "CEO", latitude: "33.1128019", longitude: "-96.6958939"},
{id: 2, name: "Payal", title: "Employee", latitude: "33.0460488", longitude: "-96.9983386"}];
var addressPoints = [];
for (i = 0; i < addressData.length; i++) {
var marker = {
latitude: addressData[i].latitude,
longitude: addressData[i].longitude,
coverage: addressData[i]
};
addressPoints.push(marker);
}
var map = L.map('map').setView(["32.9602172", "-96.7036844"], 5);
var basemap = L.tileLayer('http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', {attribution: '© OpenStreetMap © CartoDB', subdomains: 'abcd'});
basemap.addTo(map);
var markers = L.markerClusterGroup();
for (var i = 0; i < addressPoints.length; i++) {
// var icon1 = "app/common_assest/images/pin/redPin.png"; // set ehere you own marker pin whatever you want to set
var currentMarker = addressPoints[i];
console.log(currentMarker);
var contentString = '<div class="mapinfoWindowContent">' +
'<div class="mapInfoTitle">Name: ' + currentMarker.coverage.name + '</div>' +
'<div class="mapInfoSubText">Licence: ' + currentMarker.coverage.title + '</div>' +
'</div>';
// var myIcon = L.icon({// set ehere you own marker pin whatever you want to set
// iconUrl: icon1,
// iconRetinaUrl: icon1,
// });
var marker = L.marker(new L.LatLng(currentMarker['latitude'], currentMarker['longitude']), {
title: currentMarker.coverage.name
});
marker.bindPopup(contentString);
markers.addLayer(marker);
}
markers.addTo(map);
</script>
</body>
Hope fully it will help to you easily.
The solution I've used is pretty simple. Just use #googlemaps/markerclusterer library in combination with the Maps JavaScript API.
Than you will need just one line after the map is filled out with your markers:
new MarkerClusterer({ map, markers });
All information can be found here
https://developers.google.com/maps/documentation/javascript/marker-clustering
I'm using Android's Map Cluster. These are the libs I'm using:
implementation 'com.google.android.gms:play-services-places:16.0.0'
implementation 'com.google.android.gms:play-services-maps:16.0.0'
implementation 'com.google.android.gms:play-services-location:16.0.0'
implementation 'com.google.maps.android:android-maps-utils:2.0.1'
The problem I was running into is that the Cluster Markers don't separate if two items have the exact same Latitude and Longitudinal points. My fix is to scan through my array of items and if two positions match, I move their positions slightly. Here's my code:
Field Variables:
private ArrayList<Tool> esTools;
When you're done initializing the ArrayList of Tools. From your parsing method, call this:
loopThroughToolsListAndFixOnesThatHaveSameGeoPoint_FixStackingIssue();
Where the magic happens:
private void loopThroughToolsListAndFixOnesThatHaveSameGeoPoint_FixStackingIssue() {
DecimalFormat decimalFormatTool = new DecimalFormat("000.0000");
decimalFormatTool.setRoundingMode(RoundingMode.DOWN);
for(int backPointer=0; backPointer <= (esTools.size()-1); backPointer++){
Map<String, Double> compareA = esTools.get(backPointer).getUserChosenGeopoint();
Double compareA_Latitude = compareA.get("_latitude");
compareA_Latitude= Double.valueOf(decimalFormatTool.format(compareA_Latitude));
Double compareA_Longitude = compareA.get("_longitude");
compareA_Longitude= Double.valueOf(decimalFormatTool.format(compareA_Longitude));
System.out.println("compareA_Lat= "+ compareA_Latitude+ ", compareA_Long= "+ compareA_Longitude);
for(int frontPointer=0; frontPointer <= (esTools.size()-1); frontPointer++){
if(backPointer==frontPointer){
continue;
}
Map<String, Double> compareB = esTools.get(frontPointer).getUserChosenGeopoint();
Double compareB_Latitude = compareB.get("_latitude");
compareB_Latitude= Double.valueOf(decimalFormatTool.format(compareB_Latitude));
Double compareB_Longitude = compareB.get("_longitude");
compareB_Longitude= Double.valueOf(decimalFormatTool.format(compareB_Longitude));
if((compareB_Latitude.equals(compareA_Latitude)) && (compareB_Longitude.equals(compareA_Longitude))) {
System.out.println("these tools match");
Random randomGen = new Random();
Double randomNumLat = randomGen.nextDouble() * 0.00015;
int addOrSubtractLatitude= ( randomGen.nextBoolean() ? 1 : -1 );
randomNumLat = randomNumLat*addOrSubtractLatitude;
Double randomNumLong = randomGen.nextDouble() * 0.00015;
int addOrSubtractLongitude= ( randomGen.nextBoolean() ? 1 : -1 );
randomNumLong = randomNumLong*addOrSubtractLongitude;
System.out.println("Adding Random Latitude="+ randomNumLat + ", Longitude= "+ randomNumLong);
System.out.println("\n");
Map<String, Double> latitudeLongitude = new HashMap<>();
latitudeLongitude.put("_latitude", (compareB_Latitude+ randomNumLat));
latitudeLongitude.put("_longitude", (compareB_Longitude+ randomNumLong));
esTools.get(frontPointer).setUserChosenGeopoint(latitudeLongitude);
}
}
}
}
So what the above method does is scan through my ArrayList and see if there are any two Tools have matching points. If the Lat Long points match, move one slightly.
Expanding on the answers given above, just ensure you set maxZoom option when initializing the map object.
Adding to above answers but offering an alternative quick solution in php and wordpress. For this example I am storing the location field via ACF and looping through the posts to grab that data.
I found that storing the lat / lng in an array and check the value of that array to see if the loop matches, we can then update the value within that array with the amount we want to shift our pips by.
//This is the value to shift the pips by. I found this worked best with markerClusterer
$add_to_latlng = 0.00003;
while ($query->have_posts()) {
$query->the_post();
$meta = get_post_meta(get_the_ID(), "postcode", true); //uses an acf field to store location
$lat = $meta["lat"];
$lng = $meta["lng"];
if(in_array($meta["lat"],$lat_checker)){ //check if this meta matches
//if matches then update the array to a new value (current value + shift value)
// This is using the lng value for a horizontal line of pips, use lat for vertical, or both for a diagonal
if(isset($latlng_storer[$meta["lng"]])){
$latlng_storer[$meta["lng"]] = $latlng_storer[$meta["lng"]] + $add_to_latlng;
$lng = $latlng_storer[$meta["lng"]];
} else {
$latlng_storer[$meta["lng"]] = $meta["lng"];
$lng = $latlng_storer[$meta["lng"]];
}
} else {
$lat_checker[] = $meta["lat"]; //just for simple checking of data
$latlng_storer[$meta["lat"]] = floatval($meta["lat"]);
$latlng_storer[$meta["lng"]] = floatval($meta["lng"]);
}
$entry[] = [
"lat" => $lat,
"lng" => $lng,
//...Add all the other post data here and use this array for the pips
];
} // end query
Once I've grabbed these locations I json encode the $entry variable and use that within my JS.
let locations = <?=json_encode($entry)?>;
I know this is a rather specific situation but I hope this helps someone along the line!
Extending answers above, when you got joined strings, not added/subtracted position (e.g. "37.12340-0.00069"), convert your original lat/longitude to floats, e.g. using parseFloat(), then add or subtract corrections.
My question is at the very end of the post.
I have tried everything from setting a timer for all the markers to be set to all kinds of calculations of the four corners, but nothing seems to be working.
Each time that I add a marker to the markermanager, I call this function below
public function markerSetBounds(someLat , someLng):void{
var bounds:LatLngBounds = new LatLngBounds();
for(var i:int = 0; i < myMarkers.length; i++)
{
var currentLatLon:LatLng = new LatLng(someLat , someLng);
bounds.extend(currentLatLon);
}
googleMap.setZoom(googleMap.getBoundsZoomLevel(bounds));
googleMap.setCenter(bounds.getCenter());
}
I believe I know why this does not work. I am only sending one set of lat, lng at a time.
However, when I tried the following, flex told me that it did not know what myMarkers[i].lat meant.
The following is how I fill myMarkers array
var someMarker:Marker = new Marker(new LatLng(someLat , someLng), new MarkerOptions({tooltip:someAddress, hasShadow: true}));
myMarkers.push(someMarker);
This is how I want to traverse through the array, but flex does not understand what .lat means.
for(var i:int = 0; i < myMarkers.length; i++)
{
var currentLatLon:LatLng = new LatLng(myMarkers[i].lat , myMarkers[i].lng);
bounds.extend(currentLatLon);
}
My question is how do I traverse through the myMarkers array to set currentLatLon. I have also tried a for each(var someObj:Marker in myMarkers) but it finds nothing. The markers are showing up on the map, but the bounds are not working.
Have you tried doing something like:
(myMarkers[i] as Marker).lat
Is this a problem at run time or compile time?
OK, I figured out was what the issue and it was that I had to place things in the correct order.
First, declare the LatLngBounds.
Second, make the markers.
Third, set the zoom
Forth, extend the bounds.
bounds = new LatLngBounds();
covToXML = new XML(event.result);
xmlToList = new XMLList(covToXML);
listToCol = new XMLListCollection(xmlToList);
someLat = Number(listToCol.children().child("geometry").child("location").child("lat").text());
someLng = Number(listToCol.children().child("geometry").child("location").child("lng").text());
someAddress = String(listToCol.children().child("formatted_address").text());
var markerOptions:MarkerOptions = new MarkerOptions();
markerOptions.icon = new (whichIcon(GlobalVars.randomIcon));
markerOptions.tooltip = someAddress;
markerOptions.hasShadow = true;
someMarker = new Marker(new LatLng(someLat , someLng), markerOptions);
someMarker.addEventListener(MapMouseEvent.CLICK,markerClicked);
myMarkers.push(someMarker);
googleMap.addOverlay(someMarker);
for each(someMarker in myMarkers)
{
var newLatLng:LatLng = someMarker.getLatLng();
// Alert.show(newLatLng.toString());
bounds.extend(newLatLng);
}
googleMap.setCenter(bounds.getCenter());
googleMap.setZoom(googleMap.getBoundsZoomLevel(bounds));
Thanks for all the suggestions and questions, which helped me to the solution.
I have created a Sprite in Actionscript and rendered it to a Flex Canvas. Suppose:
var fooShape:Sprite = new FooSpriteSubclass();
fooCanvas.rawChildren.addChild(myshape);
//Sprite shape renders on screen
fooShape.rotation = fooNumber;
This will rotate my shape, but seems to rotate it around the upper-left
point of its parent container(the canvas).
How can I force the Sprite to rotate about is own center point? I could obviously
write code to calculate the rotation, and then have it re-render, but I think there
must be a built-in way to do this, and certainly do not want to 'reinvent the wheel'
if possible.
I am using FlexBuilder, and therefore do not have access to the full Flash API.
Thank you much!
The following steps are required to rotate objects based on a reference point (using Matrix object and getBounds):
Matrix translation (moving to the reference point)
Matrix rotation
Matrix translation (back to original position)
For example to rotate an object 90 degrees around its center:
// Get the matrix of the object
var matrix:Matrix = myObject.transform.matrix;
// Get the rect of the object (to know the dimension)
var rect:Rectangle = myObject.getBounds(parentOfMyObject);
// Translating the desired reference point (in this case, center)
matrix.translate(- (rect.left + (rect.width/2)), - (rect.top + (rect.height/2)));
// Rotation (note: the parameter is in radian)
matrix.rotate((90/180)*Math.PI);
// Translating the object back to the original position.
matrix.translate(rect.left + (rect.width/2), rect.top + (rect.height/2));
Key methods used:
Matrix.rotate
Matrix.translate
DisplayObject.getBounds
Didn't have much luck with the other examples. This one worked for me. I used it on a UIComponent.
http://www.selikoff.net/2010/03/17/solution-to-flex-image-rotation-and-flipping-around-center/
private static function rotateImage(image:Image, degrees:Number):void {
// Calculate rotation and offsets
var radians:Number = degrees * (Math.PI / 180.0);
var offsetWidth:Number = image.contentWidth/2.0;
var offsetHeight:Number = image.contentHeight/2.0;
// Perform rotation
var matrix:Matrix = new Matrix();
matrix.translate(-offsetWidth, -offsetHeight);
matrix.rotate(radians);
matrix.translate(+offsetWidth, +offsetHeight);
matrix.concat(image.transform.matrix);
image.transform.matrix = matrix;
}
Actually I had to add this code to make above solutions work for me.
private var _rotateCount = 0;
var _origginalMatrix:Matrix=new Matrix();
.........
if (_rotateCount++ >= 360 / angleDegrees)
{
myObject.transform.matrix = _origginalMatrix;
_rotateCount = 0;
return;
}
var matrix:Matrix = myObject.transform.matrix;
....
Without that after some long time rotated object slowly moves somewhere right top.
An alternative solution is to put your object inside another View, move it so that your image's center is at the container's top-left corner, and then rotate the container.
import spark.components.*;
var myContainer:View = new View();
var myImage:Image = new Image();
myContainer.addElement(myImage);
myImage.x = myImage.width / -2;
myImage.y = myImage.height / -2;
addElement(myContainer);
myContainer.rotation = whateverAngle;
One issue might be that the width of the image isn't know at the moment it is created, so you might want to find a way around that. (Hardcode it, or see if myImage.preliminaryWidth works)
/**
* Rotates the object based on its center
* Parameters: #obj => the object to rotate
* # rotation => angle to rotate
* */
public function RotateAroundCenter(obj:Object, rotation:Number):void
{
var bound:Rectangle = new Rectangle();
// get the bounded rectangle of objects
bound = obj.getRect(this);
// calculate mid poits
var midx1:Number = bound.x + bound.width/2;
var midy1:Number = bound.y + bound.height/2;
// assign the rotation
obj.rotation = rotation;
// assign the previous mid point as (x,y)
obj.x = midx1;
obj.y = midy1;
// get the new bounded rectangle of objects
bound = obj.getRect(this);
// calculate new mid points
var midx2:Number = bound.x + bound.width/2;
var midy2:Number = bound.y + bound.height/2;
// calculate differnece between the current mid and (x,y) and subtract
//it to position the object in the previous bound.
var diff:Number = midx2 - obj.x;
obj.x -= diff;
diff = midy2 - obj.y;
obj.y -= diff;
}
//////////////////
Usage:
you can use the above function as described below,
var img:Canvas = new Canvas()
RotateAroundCenter(img, rotation);
This will help you
REf: http://subashflash.blogspot.in/2010/08/rotation-of-object-based-on-center.html
If you want to rotate around the center, merely center the asset inside your sprite by setting the internal assets x and y to half of the width and height of the asset. This swill center your content and allow it to rotate around a center point.
An example of runtime loaded assets is as follows:
var loader:Loader = new Loader():
var request:URLRequest = new URLRequest(path/to/asset.ext);
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, _onLoaderComplete);
loader.load(request);
private function _onLoaderComplete(e:Event):void
{
var mc:MovieClip = e.target.content as MovieClip;
mc.x = -mc.width * 0.5;
mc.y = -mc.height * 0.5;
mc.rotation = 90;
addChild(mc);
}