I am currently using Cytoscape JS to visualize a network but I would like to overlay the graph on a world map and plot the nodes to specific locations. The Tokyo Railways Demo is similar to what I have imagined but the background is black. I want my map to look like the google maps and have similar zoom in and out capabilities. It would be great if there is a way to integrate the google maps in the Cytoscape graph.
Cytoscape.js on Google Maps, a demo code based on a sample overlay-simple. The current state is rough, but it works.
Try running the code below in full screen. To zoom IN/OUT, use press_Ctrl + scroll_wheel.
The best platform to test this code is jsfiddle.net, by forking the original code and replacing parts with the code presented here.
/*
Status: google_APIkey is good for development only
*/
// This example creates a custom overlay called CyOverlay, containing
// a U.S. Geological Survey (USGS) image of the relevant area on the map.
// Set the custom overlay object's prototype to a new instance
// of OverlayView. In effect, this will subclass the overlay class therefore
// it's simpler to load the API synchronously, using
// google.maps.event.addDomListener().
// Note that we set the prototype to an instance, rather than the
// parent class itself, because we do not wish to modify the parent class.
// some data here
var cyto_divid = 'cy'; //outside, below gmaps
var goverlay_id = 'cy4'; //overlay on gmaps
var timeoutsec = 500; //millisec
var cy;
var base_dc = 0.03; //TODO: derive a formula
var lon_ext = 25; //extent in lng
var lonmin = 90.0; //90
var latmin = 0; //0
var lon_cor = lon_ext*base_dc;
var lat_ext = lon_ext; //extent in lat
var lonmax = lonmin + lon_ext;
var lonmid = (lonmin+lonmax)/2;
var lonmax2 = lonmax + lon_cor;
var latmax = latmin + lat_ext;
var latmid = (latmin+latmax)/2;
var latlng = { lat: latmin, lng: lonmin};
var latlng2 = { lat: latmax, lng: lonmax2};
var clatlng = { lat: latmid, lng: lonmid};
var cbottom = { lat: latmin, lng: lonmid};
function lnglat2xy(lon, lat) {
let w = 500;
let h = 500;
let L = lonmax2-lonmin;
let B = latmax-latmin;
let y = (B-(lat-latmin))*h/B;
let x = (lon-lonmin)*w/L;
return {x: x, y: y};
}
var bkk = lnglat2xy(100.494, 13.75);
var bda = lnglat2xy(95.502586, 5.412598);
var hoc = lnglat2xy(106.729, 10.747);
var han = lnglat2xy(105.90673, 21.03176);
var chi = lnglat2xy(99.837, 19.899);
var nay = lnglat2xy(96.116, 19.748);
var overlay;
CyOverlay.prototype = new google.maps.OverlayView();
// Initialize the map and the custom overlay.
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 5, //11,
center: {
lat: (latmax+latmin)/2, //62.323907,
lng: (lonmax2+lonmin)/2 //-150.109291
},
mapTypeId: 'roadmap' //
});
map.addListener('zoom_changed', function() {
setTimeout(function delay() {
cy.fit(cy.$('#LL, #UR'));
}, timeoutsec)
});
var bounds = new google.maps.LatLngBounds(
new google.maps.LatLng(latmin, lonmin), //62.281819, -150.287132)
new google.maps.LatLng(latmax, lonmax2)); //62.400471, -150.005608))
// The photograph is courtesy of the U.S. Geological Survey.
var srcImage;
/*srcImage = 'https://developers.google.com/maps/documentation/' +
'javascript/examples/full/images/talkeetna.png';*/
// The custom CyOverlay object contains the USGS image,
// the bounds of the image, and a reference to the map.
overlay = new CyOverlay(bounds, srcImage, map);
// init cytoscape
setTimeout(function delay() {
cyto_run();
}, timeoutsec)
}
/** #constructor */
function CyOverlay(bounds, image, map) {
// Initialize all properties.
this.bounds_ = bounds;
this.image_ = image;
this.map_ = map;
// Define a property to hold the image's div. We'll
// actually create this div upon receipt of the onAdd()
// method so we'll leave it null for now.
this.div_ = null;
// Explicitly call setMap on this overlay.
this.setMap(map);
}
/**
* onAdd is called when the map's panes are ready and the overlay has been
* added to the map.
*/
CyOverlay.prototype.onAdd = function() {
var div = document.createElement('div');
div.id = goverlay_id;
div.style.borderStyle = 'dashed';
//div.style.backgroundColor = rgba(76,76,76,0.25);
div.style.opacity = 0.8;
div.style.borderWidth = '1px';
div.style.borderColor = 'gray';
div.style.position = 'absolute';
// Create the img element and attach it to the div.
var img = document.createElement('img');
img.src = this.image_;
img.style.width = '100%';
img.style.height = '100%';
img.style.position = 'absolute';
//div.appendChild(img); //ignore overlay img
this.div_ = div;
// Add the element to the "overlayLayer" pane.
var panes = this.getPanes();
panes.overlayLayer.appendChild(div);
};
CyOverlay.prototype.draw = function() {
// We use the south-west and north-east
// coordinates of the overlay to peg it to the correct position and size.
// To do this, we need to retrieve the projection from the overlay.
var overlayProjection = this.getProjection();
// Retrieve the south-west and north-east coordinates of this overlay
// in LatLngs and convert them to pixel coordinates.
// We'll use these coordinates to resize the div.
var sw = overlayProjection.fromLatLngToDivPixel(this.bounds_.getSouthWest());
var ne = overlayProjection.fromLatLngToDivPixel(this.bounds_.getNorthEast());
// Resize the image's div to fit the indicated dimensions.
var div = this.div_;
div.style.left = sw.x + 'px';
div.style.top = ne.y + 'px';
div.style.width = (ne.x - sw.x) + 'px';
div.style.height = (sw.y - ne.y) + 'px';
/*
cytoscape needs regen here
*/
setTimeout(function delay() {
cy.fit(cy.$('#LL, #UR'));
}, timeoutsec)
};
// The onRemove() method will be called automatically from the API if
// we ever set the overlay's map property to 'null'.
CyOverlay.prototype.onRemove = function() {
this.div_.parentNode.removeChild(this.div_);
this.div_ = null;
};
google.maps.event.addDomListener(window, 'load', initMap);
/* ___________cytoscape.js code____________ */
function cyto_run() {
cy = cytoscape({
container: document.getElementById(goverlay_id),
elements: {
nodes: [
{data: {id: "LL", nname: "LowerLeft"},
classes: "controlpoint",
position: {x: 0, y: 500}},
{data: {id: "UL", nname: "UpperLeft"},
classes: "controlpoint",
position: {x: 0, y: 0}},
{data: {id: "UR", nname: "UpperRight"},
classes: "controlpoint",
position: {x: 500, y: 0}},
{data: {id: "LR", nname: "LowerRight"},
classes: "controlpoint",
position: {x: 500, y: 500}},
{data: {id: "bkk", name: "Bangkok"}, classes: "datapoint", position: {x: bkk.x, y: bkk.y}},
{data: {id: "bda", name: "Banda"}, classes: "datapoint", position: {x: bda.x, y: bda.y}},
{data: {id: "hoc", name: "Hochi"}, classes: "datapoint", position: {x: hoc.x, y: hoc.y}},
{data: {id: "han", name: "Hanoi"}, classes: "datapoint", position: {x: han.x, y: han.y}},
{data: {id: "nay", name: "Nay"}, classes: "datapoint", position: {x: nay.x, y: nay.y}},
],
edges: [
{data: {source: "nay", target: "bda", label: "NAB"},
classes: 'autorotate'},
{data: {source: "nay", target: "han", label: "NAH"},
classes: 'autorotate'},
{data: {source: "bda", target: "bkk", label: "dgfh"}},
{data: {source: "hoc", target: "bkk", label: "tert"}},
{data: {source: "hoc", target: "bda", label: "HOB"},
classes: 'autorotate'},
{data: {source: "hoc", target: "han", label: "HOH"},
classes: 'autorotate'},
{data: {source: "han", target: "bkk", label: "terf"}},
{data: {id: "LLUR" , source: "LL", target: "UR"},
classes: "controlline"},
{data: {id: "ULLR" , source: "UL", target: "LR"},
classes: "controlline"},
]
},
style: [
{
selector: "node.datapoint",
style: {
shape: "ellipse",
width: '12px',
height: '12px',
"background-color": "blue",
label: "data(name)",
opacity: 0.85,
"text-background-color": "yellow"
}
},
{
selector: "node.controlpoint",
style: {
shape: "triangle",
width: '4px',
height: '4px',
"background-color": "blue",
label: "data(id)",
opacity: 0.85,
}
},
{
selector: "edge[label]",
style: {
label: "data(label)",
width: 2,
"line-color": "#909",
"target-arrow-color": "#f00",
"curve-style": "bezier",
"target-arrow-shape": "vee",
//"target-arrow-fill": "red",
"arrow-scale": 1.0,
}
},
{
"selector": ".autorotate",
"style": {
"edge-text-rotation": "autorotate"
}
},
{
"selector": ".controlline",
"style": {
width: "2px",
"line-color": "green",
opacity: 0.35
}
},
{
selector: ".highlight",
css: {
"background-color": "yellow"
}
}
],
layout: {
name: "preset"
}
});
}
/* Always set the map height explicitly to define the size of the div
* element that contains the map. */
#map {
height: 90%;
}
/* Optional: Makes the sample page fill the window. */
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
#cy {
width: 600px;
height: 600px;
position: absolute;
}
#cy4 {
width: 600px;
height: 600px;
position: absolute;
}
<b>Cytoscape on Google Maps</b>
<div id="map"></div>
<div id="cy"></div>
<!-- Replace the value of the key parameter with your own API key. -->
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.9.2/cytoscape.min.js"></script>
There is a plugin cytoscape-mapbox-gl, which intergrates Cytoscape.js and Mapbox GL JS. It can be used with any raster or vector basemap layer.
I'm the author.
Related
Using samples from Customizing the Map I created two custom styled map types. Now my map has four tabs.
I would like to display green markers on the green map (tab), and red markers on the red map (tab). I haven't seen a similar question, is this even possible with this approach or do I need to look into something else?
EDIT: during preview I could see red and green maps (tabs) but after posting they are not showing in the snippet. There should be four options for the map:
Map Satellite red green
Not sure why it looks different after posting.
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
center: new google.maps.LatLng(51, 4),
zoom:8,
mapTypeControlOptions: {
mapTypeIds: ["roadmap", "satellite", "red", "green"],
},
});
var red = new google.maps.StyledMapType(
[
{
"featureType": "administrative",
"stylers": [
{
"visibility": "on"
}
]
}
],
{ name: "red" }
);
map.mapTypes.set("red", red);
map.setMapTypeId("red");
var green = new google.maps.StyledMapType(
[
{
"featureType": "administrative",
"stylers": [
{
"visibility": "on"
}
]
}
],
{ name: "green" }
);
map.mapTypes.set("green", green);
map.setMapTypeId("green");
var markers = [];
// make random red, yellow, blue markers
for (var i = 0; i < 20; i++) {
var latLng = new google.maps.LatLng(51.11 - Math.random(), 4.11 - Math.random());
var marker = new google.maps.Marker({
position: latLng,
icon: 'http://maps.google.com/mapfiles/ms/micons/green.png',
label: '${i}',
map: map
});
markers.push(marker);
}
for (var i = 0; i < 20; i++) {
var latLng = new google.maps.LatLng(51.11 - Math.random(),4.11 - Math.random());
var marker = new google.maps.Marker({
position: latLng,
icon: 'http://maps.google.com/mapfiles/ms/micons/yellow.png',
label: '${i}',
map: map
});
markers.push(marker);
}
for (var i = 0; i < 20; i++) {
var latLng = new google.maps.LatLng(51.11 - Math.random(),4.11 - Math.random());
var marker = new google.maps.Marker({
position: latLng,
icon: 'http://maps.google.com/mapfiles/ms/micons/red.png',
label: '${i}',
map: map
});
markers.push(marker);
}
// match cluster icon to markers
var calc = function(markers, numStyles) {
for (var i = 0; i < markers.length; i++) {
if (markers[i].getIcon().indexOf("red.png") > -1) {
return {text: markers.length, index: 3}; // index of red
}else if (markers[i].getIcon().indexOf("yellow.png") > -1) {
return {text: markers.length, index: 2}; // index of yellow
}else if (markers[i].getIcon().indexOf("green.png") > -1) {
return {text: markers.length, index: 1};// index of blue
}
}
}
// define cluster icons
var mcOptions = {gridSize: 50, maxZoom: 15, styles: [{
height: 50,
url: "https://raw.githubusercontent.com/googlearchive/js-marker-clusterer/gh-pages/images/m1.png",
width: 50
},
{
height: 50,
url: "https://raw.githubusercontent.com/googlearchive/js-marker-clusterer/gh-pages/images/m2.png",
width: 50
},
{
height: 50,
url: "https://raw.githubusercontent.com/googlearchive/js-marker-clusterer/gh-pages/images/m3.png",
width: 50
}]
};
var markerCluster = new MarkerClusterer(map, markers, mcOptions);
markerCluster.setCalculator(calc);
}
#map {
height: 80%;
}
/* Optional: Makes the sample page fill the window. */
html, body {
height: 100%;
margin: 0;
padding: 0;
}
<script defer src="https://maps.googleapis.com/maps/api/js?v=3.42&key=YOUR_API_KEY&callback=initMap"></script>
<script src="https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/markerclusterer.js"></script>
<div id="map"></div>
From the question one thing that was not overly clear is whether or not the colour of the cluster icon should be the same as the colour of the icons. The default, numerically indexed, available colours do not include green yet your code does specifically reference this colour - as an icon green is perfectly fine however. Because of this the following might only be "close" to the initial requirement as there is no Green.
If you were hosting the various cluster images on your own server it is trivial to create a new icon in green!
With that in mind perhaps the following will be of use -
<!doctype html>
<html lang='en'>
<head>
<meta charset='utf-8' />
<title>Google Maps: Clustered Markers with Custom "Map Stylers"</title>
<style>
#map{
width:800px;
height:600px;
float:none;
margin:auto;
}
.label{
padding:0.25rem;
border:2px solid gray;
border-radius:0.5rem;
background:whitesmoke;
text-align:center;
}
.blue{}
.yellow{color:black!important;background:yellow}
.red{}
.pink{color:black!important;background:pink;}
.purple{}
</style>
<script src='//developers.google.com/maps/documentation/javascript/examples/markerclusterer/markerclusterer.js'></script>
<script>
const additional_stylers=true;
// pseudo random number generator
const mt_rand=function(a,b){
return Math.floor( Math.random() * ( b - a + 1 ) + a );
};
/*
These correspond to the colours of the icons available within
the default icons found number 1-5:
https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m[N].png
There is no Green...
*/
const colours={
'Blue':{
color:'#0000ff',
compliment:'#ff8000'
},
'Yellow':{
color:'#fbf6d1',
compliment:'#f1d1fb'
},
'Red':{
color:'#f1d7d7',
compliment:'#d89595'
},
'Pink':{
color:'#f08194',
compliment:'#f09381'
},
'Purple':{
color:'#9365f7',
compliment:'#a387de'
}
};
function initMap(){
/*
set the map with basic options
- append the new MapTypeIds
*/
let latlng=new google.maps.LatLng( 51, 4 );
let options = {
zoom:8,
center:latlng,
mapTypeId:google.maps.MapTypeId.ROADMAP,
mapTypeControlOptions:{
mapTypeIds:[ 'roadmap' ].concat( Object.keys( colours ) ),
}
};
const map = new google.maps.Map( document.getElementById('map'), options );
// set up the colours for themes and icons/markers
let rgb={};
/*
for each colour, add a new map styler and
create some markers - these are not yet
added to the map.
*/
Object.keys(colours).forEach( ( colour,index ) => {
/* set the custom style rules */
let rules=[
{ featureType:'administrative', elementType:'all',stylers:[ { visibility:'on' } ] }
];
if( additional_stylers ){
rules.push({ featureType:'road', elementType:'geometry', stylers:[ { color:colours[colour].compliment } ] });
rules.push({ featureType:'water', elementType:'geometry.fill', stylers:[ { color:colours[colour].color } ] });
/* other rules... */
};
rgb[colour]={
'markers':[],
'style':new google.maps.StyledMapType( rules, { name:colour } )
};
map.mapTypes.set( colour, rgb[colour].style );
/*
generate a varying number of markers for each
cluster colour || or just 20 as per original...
*/
for( let i=0; i < ( 20 * ( index + 1 + mt_rand(1,10) ) ); i++ ){
let mkr=new google.maps.Marker({
position: new google.maps.LatLng( 51.11 - Math.random(), 4.11 - Math.random() ),
colour:colour,
title:i.toString(),
icon:{
url:'//maps.google.com/mapfiles/ms/micons/'+colour.toLowerCase()+'.png',
labelOrigin:new google.maps.Point(16,-15)
},
label:{
text:i.toString(),
fontFamily:'monospace',
fontSize:'1rem',
fontWeight:'bold',
color:colour,
className:[ 'label', colour.toLowerCase() ].join(' ')
}
});
// store the new marker
rgb[colour].markers.push( mkr );
}
});
const calculator=function( markers, numStyles ) {
return {
text:[ markers[0].colour, markers.length ].join( String.fromCharCode(32) ),
index:Object.keys(colours).indexOf( markers[0].colour ) + 1
}
};
/*
Create the clusterer with NO markers
and assign the (undocumented) calculator.
The calculator function has been simplified
using the array of colours defined earlier.
*/
let oCluster=new MarkerClusterer( map, [], {
imagePath:'//developers.google.com/maps/documentation/javascript/examples/markerclusterer/m',
});
oCluster.setCalculator( calculator );
/*
Listen for maptype change events
and initialise the clusterer with
the respective set of markers if,
and only if, the selected maptype
is a named colour.
*/
map.addListener('maptypeid_changed',()=>{
let colour=map.getMapTypeId();
oCluster.clearMarkers();
if( Object.keys(colours).includes( colour ) ){
oCluster.addMarkers( rgb[colour].markers, false );
oCluster.fitMapToMarkers();
}
});
}
</script>
<script async defer src='//maps.googleapis.com/maps/api/js?key=APIKEY&callback=initMap'></script>
</head>
<body>
<div id='map'></div>
</body>
</html>
Proof of concept jsFiddle
I'm using this code to display an openstreetmap. It works great on a desktop display but it's not very responsive friendly. I like to have a square sized map. I'm having some problems to find the right parameters for a responsive hight and width.
How can I set it to a 1:1 ratio?
My demo of the below source code: https://jsfiddle.net/uyn9posg/
<link id="cf7-map-field-leaflet-css" rel="stylesheet" href="https://unpkg.com/leaflet#1.7.1/dist/leaflet.css" />
<script id="cf7-map-field-leaflet-js" src="https://unpkg.com/leaflet#1.7.1/dist/leaflet.js"></script>
<div style="position:relative">
<div id="CF7MapFieldDiv" style="height:600px;width:100%"></div>
<span style="position:absolute;right:0px;bottom:20px;font: 12px Arial,Helvetica,sans-serif;background-color: rgba(255, 255, 255, 0.698);padding:2px 7px;z-index: 1000;" >
Marker bei: <span id="CF7MapMarkerAt">none</span>
</span>
</div>
<script>
var map;
var marker;
function updateMarkerPosition(e) {
//var markerLatLang = [e.lat.toFixed(6), e.lng.toFixed(6)].join(',');
var markerLong = e.lng.toFixed(6);
var markerLat = e.lat.toFixed(6);
//document.getElementById('CF7MapMarkerAt').innerHTML = markerLatLang;
document.getElementById('CF7MapMarkerAt').innerHTML = "Lat="+markerLat +", Long="+markerLong;
var hidd = document.getElementById('CF7MapLocationHidden');
var hidd2 = document.getElementById('CF7MapLocationHidden_long');
var hidd3 = document.getElementById('CF7MapLocationHidden_lat');
var hidd4 = document.getElementById('CF7MapLocationHidden_zoom');
//var val = [map.getZoom(), markerLatLang].join(';');
var zoomstufe = map.getZoom();
//if (!!hidd) { hidd.value = val; }
if (!!hidd2) { hidd2.value = markerLong; }
if (!!hidd3) { hidd3.value = markerLat; }
if (!!hidd4) { hidd4.value = zoomstufe; }
}
function onMarkerDrag(e) {
updateMarkerPosition(marker.getLatLng());
}
function onMapClick(e) {
map.removeLayer(initMarker);
if (marker === undefined) {
var markerIcon = L.icon({
iconUrl: 'http://cdn.leafletjs.com/leaflet-0.4.4/images/marker-icon.png',
shadowUrl: 'http://cdn.leafletjs.com/leaflet-0.4.4/images/marker-shadow.png',
iconSize: [25, 41],
shadowSize: [41, 41],
shadowAnchor: [15, 41]
});
marker = L.marker(e.latlng, {
icon: markerIcon,
draggable: true
}).addTo(map);
marker.on('drag', onMarkerDrag);
} else {
marker.setLatLng([e.latlng.lat, e.latlng.lng]);
}
updateMarkerPosition(e.latlng);
}
var initMarker = {};
function initmap() {
// set up the map
map = new L.Map('CF7MapFieldDiv');
// create the tile layer with correct attribution
var mapUrl = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
var mapAttrib = '© OpenStreetMap contributors';
var mapTile = new L.TileLayer(mapUrl, {
minZoom: 2,
maxZoom: 18,
attribution: mapAttrib
});
map.addLayer(mapTile);
// set default view (London)
//map.setView(new L.LatLng(51.501, -0.105), 8);
map.setView(new L.LatLng(47.77929097015571, 9.609822830498674), 9);
initMarker = L.marker([47.77929097015571, 9.609822830498674], {
iconUrl: 'http://cdn.leafletjs.com/leaflet-0.4.4/images/marker-icon.png',
shadowUrl: 'http://cdn.leafletjs.com/leaflet-0.4.4/images/marker-shadow.png',
iconSize: [25, 41],
shadowSize: [41, 41],
shadowAnchor: [15, 41], draggable: true
}).addTo(this.map);
// add events
map.on('click', onMapClick);
}
initmap();
</script>
Constraining to the Smallest Viewport Dimension
Not sure if this would be an alright way of doing for your use case. But I usually like to take the smaller (minimum) of the viewport height viewport width, min(100vh,100vw) and use that to set the height and width of the element I'm trying to keep a square 1:1 aspect ratio. This will also work for other fractions of viewport height and width. Effectively, this will contain the square to the smallest dimension. Press the blue Run code snippet button below to see the results and test the responsiveness using the full page link:
Minimal Example:
.Rectangle {
--Dimensions: min(80vh, 80vw);
height: var(--Dimensions);
width: var(--Dimensions);
position: absolute;
top: calc(50% - var(--Dimensions)/2);
left: calc(50% - var(--Dimensions)/2);
background-color: cyan;
}
<div class="Rectangle"></div>
Map Implementation:
<link id="cf7-map-field-leaflet-css" rel="stylesheet" href="https://unpkg.com/leaflet#1.7.1/dist/leaflet.css" />
<script id="cf7-map-field-leaflet-js" src="https://unpkg.com/leaflet#1.7.1/dist/leaflet.js"></script>
<div style="position:relative">
<div id="CF7MapFieldDiv" style="height:min(100vh,100vw); width:min(100vh,100vw); left: calc(50% - min(100vh,100vw)/2)"></div>
<span style="position:absolute;right:0px;bottom:20px;font: 12px Arial,Helvetica,sans-serif;background-color: rgba(255, 255, 255, 0.698);padding:2px 7px;z-index: 1000;" >
Marker bei: <span id="CF7MapMarkerAt">none</span>
</span>
</div>
<script>
var map;
var marker;
function updateMarkerPosition(e) {
//var markerLatLang = [e.lat.toFixed(6), e.lng.toFixed(6)].join(',');
var markerLong = e.lng.toFixed(6);
var markerLat = e.lat.toFixed(6);
//document.getElementById('CF7MapMarkerAt').innerHTML = markerLatLang;
document.getElementById('CF7MapMarkerAt').innerHTML = "Lat="+markerLat +", Long="+markerLong;
var hidd = document.getElementById('CF7MapLocationHidden');
var hidd2 = document.getElementById('CF7MapLocationHidden_long');
var hidd3 = document.getElementById('CF7MapLocationHidden_lat');
var hidd4 = document.getElementById('CF7MapLocationHidden_zoom');
//var val = [map.getZoom(), markerLatLang].join(';');
var zoomstufe = map.getZoom();
//if (!!hidd) { hidd.value = val; }
if (!!hidd2) { hidd2.value = markerLong; }
if (!!hidd3) { hidd3.value = markerLat; }
if (!!hidd4) { hidd4.value = zoomstufe; }
}
function onMarkerDrag(e) {
updateMarkerPosition(marker.getLatLng());
}
function onMapClick(e) {
map.removeLayer(initMarker);
if (marker === undefined) {
var markerIcon = L.icon({
iconUrl: 'http://cdn.leafletjs.com/leaflet-0.4.4/images/marker-icon.png',
shadowUrl: 'http://cdn.leafletjs.com/leaflet-0.4.4/images/marker-shadow.png',
iconSize: [25, 41],
shadowSize: [41, 41],
shadowAnchor: [15, 41]
});
marker = L.marker(e.latlng, {
icon: markerIcon,
draggable: true
}).addTo(map);
marker.on('drag', onMarkerDrag);
} else {
marker.setLatLng([e.latlng.lat, e.latlng.lng]);
}
updateMarkerPosition(e.latlng);
}
var initMarker = {};
function initmap() {
// set up the map
map = new L.Map('CF7MapFieldDiv');
// create the tile layer with correct attribution
var mapUrl = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
var mapAttrib = '© OpenStreetMap contributors';
var mapTile = new L.TileLayer(mapUrl, {
minZoom: 2,
maxZoom: 18,
attribution: mapAttrib
});
map.addLayer(mapTile);
// set default view (London)
//map.setView(new L.LatLng(51.501, -0.105), 8);
map.setView(new L.LatLng(47.77929097015571, 9.609822830498674), 9);
initMarker = L.marker([47.77929097015571, 9.609822830498674], {
iconUrl: 'http://cdn.leafletjs.com/leaflet-0.4.4/images/marker-icon.png',
shadowUrl: 'http://cdn.leafletjs.com/leaflet-0.4.4/images/marker-shadow.png',
iconSize: [25, 41],
shadowSize: [41, 41],
shadowAnchor: [15, 41], draggable: true
}).addTo(this.map);
// add events
map.on('click', onMapClick);
}
initmap();
</script>
I want to add custom background image to my draggable polygons in google Map. I've already used the Polygon class to make a draggable polygon that can also rotate. I want to add background image to it. I've read other posts and they mentioned "custom overlay" but that is a fixed image on the map which doesn't support dragging/rotation. How should I go about doing this?
Update:
I created a custom layer with my image and added it to the map with the same coordinates as the polygon. Whenever the bounds of my polygon change, I will also update my custom layer so they always overlap. However, as shown in the gif, https://imgur.com/3oaktIY, the polygon and the image are not in sync and there's a delay.
Is there any other way to do it?
Did not find solutions online so I figured it out myself with this working demo: draggable polygon with image. I made it with a combination of custom overlay and normal polygon library. You can click on the polygon to rotate and drag it around.
plz see jsfiddle
code snippet:
// This example adds hide() and show() methods to a custom overlay's prototype.
// These methods toggle the visibility of the container <div>.
// Additionally, we add a toggleDOM() method, which attaches or detaches the
// overlay to or from the map.
var overlay;
USGSOverlay.prototype = new google.maps.OverlayView();
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 20,
center: {
lat: 33.678,
lng: -116.243
},
mapTypeId: google.maps.MapTypeId.TERRAIN
});
var rectangle = new google.maps.Rectangle({
strokeColor: 'red',
strokeOpacity: 0,
strokeWeight: 2,
fillColor: '#FF0000',
fillOpacity: 0,
map: map,
bounds: calcBounds(map.getCenter(), new google.maps.Size(2.7, 20))
});
var rectPoly = createPolygonFromRectangle(rectangle); //create a polygom from a rectangle
rectPoly.addListener('click', function(e) {
rotatePolygon(rectPoly, 10);
rectPoly.rotation += 10;
console.log(rectPoly.rotation)
overlay.div_.style.transform = 'rotate(' + rectPoly.rotation + 'deg)';
});
rectPoly.addListener('drag', function() {
console.log('Drag end!');
let bounds = new google.maps.LatLngBounds();
var i;
// The Bermuda Triangle
let polygonCoords = rectPoly.getPath().getArray();
for (i = 0; i < polygonCoords.length; i++) {
bounds.extend(polygonCoords[i]);
}
// The Center of the Bermuda Triangle - (25.3939245, -72.473816)
center = bounds.getCenter();
overlay.bounds_ = calcBounds(center, new google.maps.Size(2.7, 20))
overlay.draw();
});
function calcBounds(center, size) {
var n = google.maps.geometry.spherical.computeOffset(center, size.height / 2, 0).lat(),
s = google.maps.geometry.spherical.computeOffset(center, size.height / 2, 180).lat(),
e = google.maps.geometry.spherical.computeOffset(center, size.width / 2, 90).lng(),
w = google.maps.geometry.spherical.computeOffset(center, size.width / 2, 270).lng();
return new google.maps.LatLngBounds(new google.maps.LatLng(s, w),
new google.maps.LatLng(n, e))
}
var srcImage = 'https://developers.google.com/maps/documentation/' +
'javascript/examples/full/images/talkeetna.png';
overlay = new USGSOverlay(rectangle.bounds, srcImage, map, rectPoly);
// The custom USGSOverlay object contains the USGS image,
// the bounds of the image, and a reference to the map.
}
/** #constructor */
function USGSOverlay(bounds, image, map, rectPoly) {
// Now initialize all properties.
this.bounds_ = bounds;
this.image_ = image;
this.map_ = map;
this.rectPoly_ = rectPoly
// Define a property to hold the image's div. We'll
// actually create this div upon receipt of the onAdd()
// method so we'll leave it null for now.
this.div_ = null;
// Explicitly call setMap on this overlay
this.setMap(map);
}
/**
* onAdd is called when the map's panes are ready and the overlay has been
* added to the map.
*/
USGSOverlay.prototype.onAdd = function() {
var div = document.createElement('div');
div.style.border = 'none';
div.style.borderWidth = '0px';
div.style.position = 'absolute';
rectPoly = this.rectPoly_;
// Create the img element and attach it to the div.
var img = document.createElement('img');
img.src = this.image_;
img.style.width = '100%';
img.style.height = '100%';
div.appendChild(img);
this.div_ = div;
// Add the element to the "overlayImage" pane.
var panes = this.getPanes();
panes.overlayImage.appendChild(this.div_);
};
USGSOverlay.prototype.draw = function() {
// We use the south-west and north-east
// coordinates of the overlay to peg it to the correct position and size.
// To do this, we need to retrieve the projection from the overlay.
var overlayProjection = this.getProjection();
// Retrieve the south-west and north-east coordinates of this overlay
// in LatLngs and convert them to pixel coordinates.
// We'll use these coordinates to resize the div.
var sw = overlayProjection.fromLatLngToDivPixel(this.bounds_.getSouthWest());
var ne = overlayProjection.fromLatLngToDivPixel(this.bounds_.getNorthEast());
// Resize the image's div to fit the indicated dimensions.
var div = this.div_;
div.style.left = sw.x + 'px';
div.style.top = ne.y + 'px';
div.style.width = (ne.x - sw.x) + 'px';
div.style.height = (sw.y - ne.y) + 'px';
};
USGSOverlay.prototype.onRemove = function() {
this.div_.parentNode.removeChild(this.div_);
};
// Set the visibility to 'hidden' or 'visible'.
function createPolygonFromRectangle(rectangle) {
var map = rectangle.getMap();
var coords = [{
lat: rectangle.getBounds().getNorthEast().lat(),
lng: rectangle.getBounds().getNorthEast().lng()
},
{
lat: rectangle.getBounds().getNorthEast().lat(),
lng: rectangle.getBounds().getSouthWest().lng()
},
{
lat: rectangle.getBounds().getSouthWest().lat(),
lng: rectangle.getBounds().getSouthWest().lng()
},
{
lat: rectangle.getBounds().getSouthWest().lat(),
lng: rectangle.getBounds().getNorthEast().lng()
}
];
// Construct the polygon.
var rectPoly = new google.maps.Polygon({
path: coords,
draggable: true,
rotation: 0
});
var properties = ["strokeColor", "strokeOpacity", "strokeWeight", "fillOpacity", "fillColor"];
//inherit rectangle properties
var options = {};
properties.forEach(function(property) {
if (rectangle.hasOwnProperty(property)) {
options[property] = rectangle[property];
}
});
rectPoly.setOptions(options);
rectangle.setMap(null);
rectPoly.setMap(map);
return rectPoly;
}
function rotatePolygon(polygon, angle) {
var map = polygon.getMap();
var prj = map.getProjection();
var bounds = new google.maps.LatLngBounds();
var i;
// The Bermuda Triangle
var polygonCoords = polygon.getPath().getArray();
for (i = 0; i < polygonCoords.length; i++) {
bounds.extend(polygonCoords[i]);
}
// The Center of the Bermuda Triangle - (25.3939245, -72.473816)
console.log(bounds.getCenter());
var origin = prj.fromLatLngToPoint(bounds.getCenter()); //rotate around first point
//var origin2 = prj.fromLatLngToPoint(polygon.getPath().getAt(1)); //rotate around first point
var coords = polygon.getPath().getArray().map(function(latLng) {
var point = prj.fromLatLngToPoint(latLng);
var rotatedLatLng = prj.fromPointToLatLng(rotatePoint(point, origin, angle));
return {
lat: rotatedLatLng.lat(),
lng: rotatedLatLng.lng()
};
});
polygon.setPath(coords);
}
function rotatePoint(point, origin, angle) {
var angleRad = angle * Math.PI / 180.0;
return {
x: Math.cos(angleRad) * (point.x - origin.x) - Math.sin(angleRad) * (point.y - origin.y) + origin.x,
y: Math.sin(angleRad) * (point.x - origin.x) + Math.cos(angleRad) * (point.y - origin.y) + origin.y
};
}
google.maps.event.addDomListener(window, 'load', initMap);
/* Always set the map height explicitly to define the size of the div
* element that contains the map. */
#map {
height: 100%;
}
/* Optional: Makes the sample page fill the window. */
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
#floating-panel {
position: absolute;
top: 10px;
left: 25%;
z-index: 5;
background-color: #fff;
padding: 5px;
border: 1px solid #999;
text-align: center;
font-family: 'Roboto', 'sans-serif';
line-height: 30px;
padding-left: 10px;
}
<!-- Add an input button to initiate the toggle method on the overlay. -->
<div id="map"></div>
<!-- Replace the value of the key parameter with your own API key. -->
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&libraries=geometry"></script>
So I have this:
But what I want is this:
I'm pretty sure there should be an option I can specify here:
var options = {
gridSize: 80,
imagePath: imagePath ,
someOption : iAmMissing ??
}
var mc = new MarkerClusterer(map, mapmarkers, options);
google.maps.event.addListener(mc, 'clusteringend', function(){
setTimeout(fixMyPageOnce, 1);
});
But I can't seem to find an option. Is this the right place or is there another way I can get rid of the numbers in the circles?
You can just use the "styles" options and set the "textSize" option slightly above 0:
var options = {
gridSize: 80,
styles: [{
url: imagePath,
height: 29,
width: 29,
anchor: [0, 0],
textSize: 0.001
}, {
url: imagePath,
height: 49,
width: 49,
anchor: [0, 0],
textSize: 0.001
}, {
url: imagePath,
width: 65,
height: 65,
anchor: [0, 0],
textSize: 0.001
}]
}
It is working for me.
Just set textColor to transparent
var options = {
textColor: 'transparent',
gridSize: 80,
imagePath: imagePath ,
someOption : iAmMissing ??
}
Create a custom "calculator" function that sets the "text" property of the return value to "".
calculator: function(markers, numStyles) {
var index = 0;
var count = markers.length.toString();
var dv = count;
while (dv !== 0) {
dv = parseInt(dv / 10, 10);
index++;
}
index = Math.min(index, numStyles);
return {
text: "",
index: index,
title: count
};
}
proof of concept fiddle
code snippet:
function initialize() {
var center = new google.maps.LatLng(52.4357808, 4.991315699999973);
var mapOptions = {
zoom: 12,
center: center,
mapTypeId: google.maps.MapTypeId.TERRAIN
};
map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
for (i = 0; i < markers.length; i++) {
addMarker(markers[i]);
}
markerCluster = new MarkerClusterer(map, gmarkers, {
imagePath: 'https://cdn.rawgit.com/googlemaps/v3-utility-library/master/markerclustererplus/images/m',
calculator: function(markers, numStyles) {
var index = 0;
var title = "";
var count = markers.length.toString();
var dv = count;
while (dv !== 0) {
dv = parseInt(dv / 10, 10);
index++;
}
index = Math.min(index, numStyles);
return {
text: "",
index: index,
title: count
};
}
});
}
var gmarkers = [];
var markers = [];
var infowindow = new google.maps.InfoWindow({
content: ''
});
var markerCluster;
// Our markers
markers = [
['0', 'Title 1', 52.4357808, 4.991315699999973, 'car'],
['1', 'Title 2', 52.4357808, 4.981315699999973, 'third'],
['2', 'Title 3', 52.4555687, 5.039231599999994, 'car'],
['3', 'Title 4', 52.4555687, 5.029231599999994, 'second']
];
function addMarker(marker) {
var title = marker[1];
var pos = new google.maps.LatLng(marker[2], marker[3]);
var content = marker[1];
var marker = new google.maps.Marker({
title: title,
position: pos,
map: map
});
gmarkers.push(marker);
google.maps.event.addListener(marker, 'click', (function(marker1, content) {
return function() {
infowindow.setContent(content);
infowindow.open(map, marker);
}
})(marker, content));
}
google.maps.event.addDomListener(window, "load", initialize);
html,
body,
#map-canvas {
width: 100%;
height: 100%;
}
<script src="https://maps.googleapis.com/maps/api/js?libraries=geometry,places&key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk"></script>
<script src="https://unpkg.com/#googlemaps/markerclustererplus/dist/index.min.js"></script>
<div id="map-canvas"></div>
Is it possible to write a custom text on Google Maps API v3 next to the marker, or I can use only the info window to do that?
To show custom text you need to create a custom overlay. Below is an example adapted from official Google documentation. You could also use this library for more "stylish" info windows
<html>
<head>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false">
</script>
<script>
//adapded from this example http://code.google.com/apis/maps/documentation/javascript/overlays.html#CustomOverlays
//text overlays
function TxtOverlay(pos, txt, cls, map) {
// Now initialize all properties.
this.pos = pos;
this.txt_ = txt;
this.cls_ = cls;
this.map_ = map;
// We define a property to hold the image's
// div. We'll actually create this div
// upon receipt of the add() method so we'll
// leave it null for now.
this.div_ = null;
// Explicitly call setMap() on this overlay
this.setMap(map);
}
TxtOverlay.prototype = new google.maps.OverlayView();
TxtOverlay.prototype.onAdd = function() {
// Note: an overlay's receipt of onAdd() indicates that
// the map's panes are now available for attaching
// the overlay to the map via the DOM.
// Create the DIV and set some basic attributes.
var div = document.createElement('DIV');
div.className = this.cls_;
div.innerHTML = this.txt_;
// Set the overlay's div_ property to this DIV
this.div_ = div;
var overlayProjection = this.getProjection();
var position = overlayProjection.fromLatLngToDivPixel(this.pos);
div.style.left = position.x + 'px';
div.style.top = position.y + 'px';
// We add an overlay to a map via one of the map's panes.
var panes = this.getPanes();
panes.floatPane.appendChild(div);
}
TxtOverlay.prototype.draw = function() {
var overlayProjection = this.getProjection();
// Retrieve the southwest and northeast coordinates of this overlay
// in latlngs and convert them to pixels coordinates.
// We'll use these coordinates to resize the DIV.
var position = overlayProjection.fromLatLngToDivPixel(this.pos);
var div = this.div_;
div.style.left = position.x + 'px';
div.style.top = position.y + 'px';
}
//Optional: helper methods for removing and toggling the text overlay.
TxtOverlay.prototype.onRemove = function() {
this.div_.parentNode.removeChild(this.div_);
this.div_ = null;
}
TxtOverlay.prototype.hide = function() {
if (this.div_) {
this.div_.style.visibility = "hidden";
}
}
TxtOverlay.prototype.show = function() {
if (this.div_) {
this.div_.style.visibility = "visible";
}
}
TxtOverlay.prototype.toggle = function() {
if (this.div_) {
if (this.div_.style.visibility == "hidden") {
this.show();
} else {
this.hide();
}
}
}
TxtOverlay.prototype.toggleDOM = function() {
if (this.getMap()) {
this.setMap(null);
} else {
this.setMap(this.map_);
}
}
var map;
function init() {
var latlng = new google.maps.LatLng(37.9069, -122.0792);
var myOptions = {
zoom: 4,
center: latlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById("map"), myOptions);
var marker = new google.maps.Marker({
position: latlng,
map: map,
title: "Hello World!"
});
customTxt = "<div>Blah blah sdfsddddddddddddddd ddddddddddddddddddddd<ul><li>Blah 1<li>blah 2 </ul></div>"
txt = new TxtOverlay(latlng, customTxt, "customBox", map)
}
</script>
<style>
.customBox {
background: yellow;
border: 1px solid black;
position: absolute;
}
</style>
</head>
<body onload="init()">
<div id="map" style="width: 600px; height: 600px;">
</div>
</body>
</html>
By far the easiest way to add a Text Overlay is to use the MapLabel class from https://github.com/googlemaps/js-map-label
var mapLabel = new MapLabel({
text: 'Test',
position: new google.maps.LatLng(50,50),
map: map,
fontSize: 20,
align: 'right'
});
To add custom text to google maps, you can use a marker with an empty pixel for the icon:
new google.maps.Marker({
position: { lat: 0, lng: 0 },
map: map,
icon: '../res/img/empty.png',
label: {
color: '#FF0000',
fontWeight: 'bold',
text: 'Hello World',
fontSize: '20px',
},
});
Style options are listed here
It the text is static, you can use a marker and an image :
var label = new google.maps.Marker({
position: new google.maps.LatLng(50,50),
map: map,
icon: "/images/mytextasanimage.png"
});
The latest (v3) API recommends using async defer and a callback when the Maps API is loaded.
<script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"></script>
To make this work you need to define the overlay class from within (or after) the initialization when the google class has been defined. Otherwise you will get google not defined errors.
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
center: { lat: 40, lng: -30 },
zoom: 3
});
TxtOverlay.prototype = new google.maps.OverlayView();
var overlay = new TxtOverlay(new google.maps.LatLng(0, 0),
"<div>Have a wonderful overlay day</div>",
"customCSSClass",
map);
}
...
//adapded from answer above
function TxtOverlay(pos, txt, cls, map) {
// Now initialize all properties.
this.pos = pos;
this.txt_ = txt;
this.cls_ = cls;
this.map_ = map;
// We define a property to hold the image's
// div. We'll actually create this div
// upon receipt of the add() method so we'll
// leave it null for now.
this.div_ = null;
this.onAdd = function() {
// Note: an overlay's receipt of onAdd() indicates that
// the map's panes are now available for attaching
// the overlay to the map via the DOM.
// Create the DIV and set some basic attributes.
var div = document.createElement('DIV');
div.className = this.cls_;
div.innerHTML = this.txt_;
// Set the overlay's div_ property to this DIV
this.div_ = div;
var overlayProjection = this.getProjection();
var position = overlayProjection.fromLatLngToDivPixel(this.pos);
div.style.left = position.x + 'px';
div.style.top = position.y + 'px';
// We add an overlay to a map via one of the map's panes.
var panes = this.getPanes();
panes.floatPane.appendChild(div);
}
this.draw = function() {
var overlayProjection = this.getProjection();
// Retrieve the southwest and northeast coordinates of this overlay
// in latlngs and convert them to pixels coordinates.
// We'll use these coordinates to resize the DIV.
var position = overlayProjection.fromLatLngToDivPixel(this.pos);
var div = this.div_;
div.style.left = position.x + 'px';
div.style.top = position.y + 'px';
}
this.onRemove = function() {
this.div_.parentNode.removeChild(this.div_);
this.div_ = null;
}
this.hide = function() {
if (this.div_) {
this.div_.style.visibility = "hidden";
}
}
this.show = function() {
if (this.div_) {
this.div_.style.visibility = "visible";
}
}
this.toggle = function() {
if (this.div_) {
if (this.div_.style.visibility == "hidden") {
this.show();
} else {
this.hide();
}
}
}
this.toggleDOM = function() {
if (this.getMap()) {
this.setMap(null);
} else {
this.setMap(this.map_);
}
}
// Explicitly call setMap() on this overlay
this.setMap(map);
}
Here is working code:
#map {
height: 500px;
}
html, body {
height: 100%;
margin: 0;
padding: 0;
}
<div id="content">
<div id="map"></div>
<div class="centered">
<h1>Text Over Maps</h1>
</div>
</div>