Get latitude and longitude that is 5 metres away of a latitude and longitude [duplicate] - google-maps-api-3

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.

Related

recursive function on each pixel in google earth engine

I want to filter time series in the google earth engine which requires two for loops over time-series of a single pixel. I searched around and not found any example related to this. I know about .map function and I am using it for the generation of RVI on the earth engine. I found about .toArray function but not found any example related to my problem.
I will appreciate any help in this regard. Also, I am new to the earth engine so this may be a trivial question for others.
This is the code that I have. I took it from a blog and modified it according to my need. I am stuck after this.
var sentinel1 = ee.ImageCollection('COPERNICUS/S1_GRD_FLOAT');
// Filter VH, IW
var vh = sentinel1
// Filter to get images with VV and VH dual polarization.
//.filter(ee.Filter.listContains('transmitterReceiverPolarisation', 'VH'))
// Filter to get images collected in interferometric wide swath mode.
.filter(ee.Filter.eq('instrumentMode', 'IW'))
// reduce to VH polarization
//.select('VH')
// filter 10m resolution
.filter(ee.Filter.eq('resolution_meters', 10));
// Filter to orbitdirection Descending
var vhDescending = vh.filter(ee.Filter.eq('orbitProperties_pass', 'DESCENDING'));
// Filter time 2015
var vhDesc2015 = vhDescending.filterDate(ee.Date('2021-01-01'), ee.Date('2021-04-30'));
// Filter to MKD roi
var s1_mkd = vhDesc2015.filterBounds(roi);
print('All metadata:', s1_mkd);
var count = s1_mkd.size();
print('Count: ', count);
//var dates = s1_mkd.aggregate_array("system:time_start")
//print('dates: ', dates);
var dates = s1_mkd
.map(function(image) {
return ee.Feature(null, {'date': image.date().format('YYYY-MM-dd')})
})
.distinct('date')
.aggregate_array('date')
print('dates: ', dates);
var featureCollection = ee.FeatureCollection(dates
.map(function(element){
return ee.Feature(null,{prop:element})}))
//Export a .csv table of date, mean NDVI for watershed
Export.table.toDrive({
collection: featureCollection,
description: 'Timeseries',
folder: 'WC_raw',
fileFormat: 'CSV',
});
var rvi4s1 = function(img){
var vh = img.select('VH');
var vv = img.select('VV');
var col = vv.divide(vv.add(vh)).sqrt().rename('dop');
var dop = col.select('dop')
var value = dop.multiply(vh.multiply(4).divide(vv.add(vh))).rename('rvi4s1');
return value;
};
var rvi = s1_mkd.map(rvi4s1);
print(rvi);

How to pan using paperjs

I have been trying to figure out how to pan/zoom using onMouseDrag, and onMouseDown in paperjs.
The only reference I have seen has been in coffescript, and does not use the paperjs tools.
This took me longer than it should have to figure out.
var toolZoomIn = new paper.Tool();
toolZoomIn.onMouseDrag = function (event) {
var a = event.downPoint.subtract(event.point);
a = a.add(paper.view.center);
paper.view.center = a;
}
you can simplify Sam P's method some more:
var toolPan = new paper.Tool();
toolPan.onMouseDrag = function (event) {
var offset = event.downPoint - event.point;
paper.view.center = paper.view.center + offset;
};
the event object already has a variable with the start point called downPoint.
i have put together a quick sketch to test this.
Unfortunately you can't rely on event.downPoint to get the previous point while you're changing the view transform. You have to save it yourself in view coordinates (as pointed out here by Jürg Lehni, developer of Paper.js).
Here's a version that works (also in this sketch):
let oldPointViewCoords;
function onMouseDown(e) {
oldPointViewCoords = view.projectToView(e.point);
}
function onMouseDrag(e) {
const delta = e.point.subtract(view.viewToProject(oldPointViewCoords));
oldPointViewCoords = view.projectToView(e.point);
view.translate(delta);
}
view.translate(view.center);
new Path.Circle({radius: 100, fillColor: 'red'});

i tried to remove pushpins from bingmaps but when i zoom in or zoom out or grag the map, the removed pushpin appears again

I have a created a bing map and i have got success in adding and remove the pushpins but after removing the pins if i zoom in/zoom out or drag the map, the removed pushpins appears again
Here is my code
var arr = new Array();
arr = $('div[id*=Contractor]');
$.each(arr, function (index, value) {
var v = $(value).find("div[id^='distance']").text().substr(10, $(value).find("div[id^='distance']").text().indexOf('km') - 10);
if (v > 50) {
i = value.id.substr(10);
$.each(contractorPushpinObjectArray, function (index, value) {
if (contractorPushpinObjectArray[index][0] == i) {
map.entities.remove(contractorPushpinObjectArray[index][1]);
cid.push(i);
}
});
}
});
How are you adding the pushpins to the map? If you are using some sort of clustering tool you will need to remove the pins from the clustering tool otherwise the next time the clustering algorithm runs it will read the pushpins.

OpenLayer Popups for markers imported from google spreadsheet

I'm looking for a way to use framecloud type popup with my current setup. Unfortunately all my attempts have either not worked or will only work on the most recently placed maker.
In the course of trying to get it to work I have converted my original script from using Markers to using Vectors to placing the marker points (as I've seen that it's easier to customize vectors than markers.)
Now which ever one I can get to work I'll use, but after working on this for a few days I'm at my wits end and need a helping hand in the right direction.
My points are pulled from a google spreadsheet using tabletop.js. The feature is working how I wish it to, with the markers being placed on their respective layer based on a field I called 'type'.
While I have a feeling that might have been the source of my problem with the Markers type layer, I'm not sure how to fix it.
You can view the coding through these pages
(Links removed due to location change.)
Thanks for all help in advance.
I finally got it to work. For anyone in a similar situation here's my final code for the layers. I did change the names of the layers from what they are originally and blacked out the spreadsheet I used, but the changes should be noticeable.
//
//// Set 'Markers'
//
var iconMarker = {externalGraphic: 'http://www.openlayers.org/dev/img/marker.png', graphicHeight: 21, graphicWidth: 16};
var iconGeo = {externalGraphic: './images/fortress.jpg', graphicHeight: 25, graphicWidth: 25};
var iconAero = {externalGraphic: './images/aeropolae.jpg', graphicHeight: 25, graphicWidth: 25}; // Image is the creation of DriveByArtist: http://drivebyartist.deviantart.com/
var vector1 = new OpenLayers.Layer.Vector("1");
var vector2 = new OpenLayers.Layer.Vector("2");
var vector3 = new OpenLayers.Layer.Vector("3");
// Pulls map info from Spreadsheet
//*
Tabletop.init({
key: 'http://xxxxxxxxxx', //Spreadsheet URL goes here
callback: function(data, tabletop) {
var i,
dataLength = data.length;
for (i=0; i<dataLength; i++) { //following are variables from the spreadsheet
locName = data[i].name;
locLon = data[i].long;
locLat = data[i].lat;
locInfo = data[i].info;
locType = data[i].type; // Contains the following string in the cell, which provides a pre-determined output based on provided information in the spreadsheet: =ARRAYFORMULA("<h2>"&B2:B&"</h2><b>"&G2:G&"</b><br /> "&C2:C&", "&D2:D&"<br />"&E2:E&if(ISTEXT(F2:F),"<br /><a target='_blank' href='"&F2:F&"'>Read More...</a>",""))
locLonLat= new OpenLayers.Geometry.Point(locLon, locLat);
switch(locType)
{
case "Geopolae":
feature = new OpenLayers.Feature.Vector(
locLonLat,
{description:locInfo},
iconGeo);
vector1.addFeatures(feature);
break;
case "POI":
feature = new OpenLayers.Feature.Vector(
locLonLat,
{description:locInfo},
iconMarker);
vector2.addFeatures(feature);
break;
case "Aeropolae":
feature = new OpenLayers.Feature.Vector(
locLonLat,
{description:locInfo},
iconAero);
vector3.addFeatures(feature);
break;
}
}
},
simpleSheet: true
});
map.addLayers([vector1, vector2, vector3]);
map.addControl(new OpenLayers.Control.LayerSwitcher());
//Add a selector control to the vectorLayer with popup functions
var controls = {
selector: new OpenLayers.Control.SelectFeature(Array(vector1, vector2, vector3), { onSelect: createPopup, onUnselect: destroyPopup })
};
function createPopup(feature) {
feature.popup = new OpenLayers.Popup.FramedCloud("pop",
feature.geometry.getBounds().getCenterLonLat(),
null,
'<div class="markerContent">'+feature.attributes.description+'</div>',
null,
true,
function() { controls['selector'].unselectAll(); }
);
feature.popup.autoSize = true;
feature.popup.minSize = new OpenLayers.Size(400,100);
feature.popup.maxSize = new OpenLayers.Size(400,800);
feature.popup.fixedRelativePosition = true;
feature.popup.overflow ="auto";
//feature.popup.closeOnMove = true;
map.addPopup(feature.popup);
}
function destroyPopup(feature) {
feature.popup.destroy();
feature.popup = null;
}
map.addControl(controls['selector']);
controls['selector'].activate();
}

Flex/ActionScript - rotate Sprite around its center

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);
}

Resources