How to get maximum ZIndex of all markers? - google-maps-api-3

I need to get the maximum value of zindex of all markers to set firs and the las on the top, so i do it like this:
var max_marker_zindex = 0;
for (i = 1, len = map_coordinates.length - 1; i < len; i++) {
var marker = new google.maps.Marker({
position: map_coordinates[i],
map: map
});
max_marker_zindex = marker.getZIndex() > max_marker_zindex ? marker.getZIndex() : max_marker_zindex;
}
But the problem is that getZIndex always returns null and there isn't any event for marker loading state.... How i can get the maximum index of markers?

It appears the zIndex-property is undefined unless you set it on your own.
use google.maps.Marker.MAX_ZINDEX + 1 instead

Related

How to properly change the vertices position programmatically for a link in JointJS

I am changing the vertices position for the link using the following code:
var vertices = link.get('vertices');
for(var j=0;j < vertices.length; j++){
var vertex = vertices[j];
vertex.x += differenceX;
vertex.y += differenceY;
}
However, even though the link vertices seem to be nicely translated, when hovering over the link, the link-tools appear in the previous position as shown in the following screenshot:
I already tried different things including calls to
paper.render()
linkview.update()
unfortunately none of them seem to work...
You should use Backbone.Model.prototype.set to update the attributes instead of modifying them directly. Otherwise no change event is triggered on the model and the view does not update itself.
var oldVertices = link.get('vertices');
var newVertices = [];
for(var j=0; j < oldVertices.length; j++){
var oldVertex = oldVertices[j];
// Create a new object, so you are not modifying
// the previous model's attributes.
// Backbone Model would not trigger a change event if the previous
// and the new value are `deep` equal.
var newVertex = { x: oldVertex.x, y: oldVertex.y }
newVertex.x += differenceX;
newVertex.y += differenceY;
newVertices.push(newVertex);
}
link.set('vertices', newVertices);

easy way of ensuring one element is always in front of everything else?

on a website I want to have an element take up the entire space of the screen (after an event), but I can't seem to manage to get it in front of all the other elements on my website. Do I have to set positions and z-indexes for everything or is there another way of setting the element I want in front of everything else?
Brute force.
function getMaxZ() {
var all = document.querySelectorAll('*');
var len = all.length;
var maxZ = 0;
var dv = document.defaultView;
for (var i = 0; i < len ; i++) {
var el = all[i];
var thisZ = el.currentStyle ? el.currentStyle : dv.getComputedStyle(el, null);
thisZ = thisZ.zIndex.replace(/[^\d]/,'') * 1;
if (thisZ > maxZ) maxZ = thisZ;
}
return maxZ;
}
alert(getMaxZ());
If you want this thing always on top, just set its z-index to 999 (or something higher than you know everything else is).

Get latitude and longitude that is 5 metres away of a latitude and longitude [duplicate]

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.

flex actionscript LatLngBounds can not get it to work

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.

When to render Legend in Flex

My Flex chart has code that creates a legend from each data series. Currently, I have the drawLegend function execute when a button is clicked.
I am trying to get the legend to draw automatically but I am having trouble determining when to call the drawLegend function. Users click on an 'Update' button which retrieves data. I want the legend to get created after this, without the users having to click on another button.
I have put my drawLegend function in several places (ResultHandler, afterEffectEnd(for chart animation, etc.) and nothing works.
Sometime after the series has been added to the chart and after the series animation ends, the legend can be added.
How can I trap this point in time and call my function?
Any ideas?
Below is the code I used to create the legend. Note that even though the code processes each series, I noticed that the Fill color is null if it is called too early.
private function drawLegend(event:Event):void {
clearLegend();
// Use a counter for the series.
var z:int = 0;
var numRows:int;
if (chart.series.length % rowSize == 0) {
// The number of series is exactly divisible by the rowSize.
numRows = Math.floor(chart.series.length / rowSize);
} else {
// One extra row is needed if there is a remainder.
numRows = Math.floor(chart.series.length / rowSize) + 1;
}
for (var j:int = 0; j < numRows; j++) {
var gr:GridRow = new GridRow();
myGrid.addChild(gr);
for (var k:int = 0; k < rowSize; k++) {
// As long as the series counter is less than the number of series...
//skip columnset group
if (chart.series[z] is LineSeries || chart.series[z] is ColumnSeries){
if (z < chart.series.length) {
var gi:GridItem = new GridItem();
gr.addChild(gi);
var li:LegendItem = new LegendItem();
// Apply the current series' displayName to the LegendItem's label.
li.label = chart.series[z].displayName;
// Get the current series' fill.
var sc:SolidColor = new SolidColor();
sc.color = chart.series[z].items[0].fill.color;
// Apply the current series' fill to the corresponding LegendItem.
li.setStyle("fill", sc.color);//was just sc
// Apply other styles to make the LegendItems look uniform.
li.setStyle("textIndent", 5);
li.setStyle("labelPlacement", "left");
li.setStyle("fontSize", 9);
gi.setStyle("backgroundAlpha", "1");
gi.setStyle("backgroundColor", sc.color);
//gi.width = 80;
// Add the LegendItem to the GridItem.
gi.addChild(li);
}
}
// Increment any time a LegendItem is added.
z++;
}
}
}
Well, I couldn't quite figure out what event to trap so I used the following in the chart's parent container:
chart.addEventListener(ChartItemEvent.ITEM_ROLL_OVER,drawLegend);
Seems to work ok.

Resources