I have a number of trips with each having a list of positions.
I need assign a color for each trip but what's happening is that as the lines are being drawn, instead of changing the new color for each trip, it changes for all polylines already drawn.
How can I do it for each trip?
getTrips() async {
await Future.forEach(trips, (element) async {
List positions = element['positions'] as List;
await Future.forEach(positions, (element) async {
latitude = element['coordinates'][0];
longitude = element['coordinates'][1];
direction = element['direction'] ?? 0;
//Current position
position = LatLng(latitude ?? 0, longitude ?? 0);
positionList.add(position);
addMarker();
});
});}
addMarker() async {
var m = markers.firstWhere((p) => p.markerId == MarkerId(equipmentId), orElse: () => null);
if (markers.isNotEmpty) {
markers.remove(m);
}
selectedMarker = MarkerId(equipmentId);
markers
..add(Marker(
visible: true,
markerId: selectedMarker,
position: position,
icon: customIcon,
anchor: Offset(0.5, 0.5),
rotation: direction,
));
_polyline.add(Polyline(
polylineId: PolylineId(_polylineIdCounter.toString()),
visible: true,
width: 6,
points: positionList,
color: tripColors[++colorIndex % tripColors.length],
));
_polylineIdCounter++;
cPosition = CameraPosition(
zoom: cameraZoom,
target: LatLng(position.latitude, position.longitude),
);
if (mounted) setState(() {});
_animateCamera(cPosition);
}
I know it's been a while but just in case there's someone out there who needs this then here you go.
Theoretically, the polyline property is a "Set{}" which means you can have more than one polyline, so for each trip create a different polyline with a different color and add all of them to the set.
I believe that should solve it.
Related
I am loading a map from react-google-maps-api, and then based on the current zoom & center point, we calculate the radius of the map to retrieve items within the "viewable" area of the map. However, the radius being calculated seems to be larger than the visible area.
I have referenced some existing SO threads including this, this, this.
Our map is initialized like:
<GoogleMap
id="live-view-map"
mapContainerStyle={{ width: 'inherit', height: 'inherit' }}
center={userLocation} // denver, co for reference
options={{
disableDefaultUI: true,
clickableIcons: false,
fullscreenControl: false,
zoomControl: true,
}}
zoom={13}
onLoad={onLoad}
onTilesLoaded={() => {
dispatch({ type: ACTIONS.SET_MAP_LOADED, payload: true });
}}
onDragEnd={handleDrag}
onZoomChanged={handleZoomChanged}
onUnmount={() => {
dispatch({ type: ACTIONS.SET_LATLNG, payload: null });
dispatch({ type: ACTIONS.SET_MAP, payload: null });
dispatch({ type: ACTIONS.SET_MAP_LOADED, payload: false });
}}
>
// items from endpoint get rendered as markers here
</GoogleMap>
My current get radius is comparing the NE corner & SW corner to determine the smaller distance from center:
const getRadius = (): number | null => {
const bounds = state.mapInstance?.getBounds();
if (!bounds) return null;
// computeDistanceBetween returns meters
const neRadius = google.maps.geometry.spherical.computeDistanceBetween(
bounds.getCenter(),
bounds.getNorthEast()
);
const swRadius = google.maps.geometry.spherical.computeDistanceBetween(
bounds.getCenter(),
bounds.getSouthWest()
);
const radius = neRadius <= swRadius ? neRadius : swRadius;
return Number((radius * 0.000621371).toFixed(2));
};
The radius is returning as 6.39 miles. However, the map visible region is for sure around 3.5miles. I thought the radius formula was perhaps incorrect, but I feel confident it's OK. So I am wondering if there's something wrong with the map visible region?
The black circle is the “radius” calculated from the bounds of google. This function is the same whether we use a custom function to calc radius or the google API maps.geometry.spherical.computeDistanceBetween.
The red circle is the same radius value above, but divided by 2 (i had a theory that it’s like diameter instead?).
The red circle just about fits the initial map zoom level.
(screenshot is zoomed out to capture both circles)
What am I doing wrong?
A circle with a radius equal to the corners of the bounding box will extend outside the viewable area.
If you want a circle that is completely visible, use the shorter of the distances from the center of the map to the center of each side of the bonding box.
const getRadius = function() {
console.log("getRadius")
const bounds = map.getBounds();
if (!bounds) return null;
console.log("getRadius bounds="+bounds.toUrlValue(6))
// computeDistanceBetween returns meters
const nsRadius = google.maps.geometry.spherical.computeDistanceBetween(
bounds.getCenter(),
new google.maps.LatLng(bounds.getNorthEast().lat(), bounds.getCenter().lng())
);
const ewRadius = google.maps.geometry.spherical.computeDistanceBetween(
bounds.getCenter(),
new google.maps.LatLng(bounds.getCenter().lat(),bounds.getNorthEast().lng())
);
const radius = nsRadius <= ewRadius ? nsRadius : ewRadius;
return Number((radius).toFixed(2));
}
proof of concept fiddle
code snippet:
let map;
function initMap() {
// Create the map.
map = new google.maps.Map(document.getElementById("map"), {
zoom: 4,
center: {
lat: 37.09,
lng: -95.712
},
mapTypeId: "terrain",
});
google.maps.event.addListenerOnce(map, 'bounds_changed', function() {
var bounds = map.getBounds();
console.log(bounds.toUrlValue(6));
const circle = new google.maps.Circle({
strokeColor: "#FF0000",
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: "#FF0000",
fillOpacity: 0.35,
map: map,
center: bounds.getCenter(),
radius: getRadius(),
});
});
google.maps.event.addDomListener(document.getElementById('btn'), 'click', function() {
var bounds = map.getBounds();
console.log(bounds.toUrlValue(6));
const circle = new google.maps.Circle({
strokeColor: "#0000FF",
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: "#0000FF",
fillOpacity: 0.35,
map: map,
center: bounds.getCenter(),
radius: getRadius(),
});
})
}
const getRadius = function() {
console.log("getRadius")
const bounds = map.getBounds();
if (!bounds) return null;
console.log("getRadius bounds=" + bounds.toUrlValue(6))
// computeDistanceBetween returns meters
const nsRadius = google.maps.geometry.spherical.computeDistanceBetween(
bounds.getCenter(),
new google.maps.LatLng(bounds.getNorthEast().lat(), bounds.getCenter().lng())
);
const ewRadius = google.maps.geometry.spherical.computeDistanceBetween(
bounds.getCenter(),
new google.maps.LatLng(bounds.getCenter().lat(), bounds.getNorthEast().lng())
);
const radius = nsRadius <= ewRadius ? nsRadius : ewRadius;
return Number((radius).toFixed(2));
}
/* 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;
}
<!DOCTYPE html>
<html>
<head>
<title>Circles</title>
<script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>
<!-- jsFiddle will insert css and js -->
</head>
<body>
<input id="btn" type="button" value="computeBounds" />
<div id="map"></div>
<!-- Async script executes immediately and must be after any DOM elements used in callback. -->
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&callback=initMap&libraries=geometry&v=weekly&channel=2" async></script>
</body>
</html>
I have a Google Map with a set of markers on. I'm trying to add MarkerClusterer to it to simplify the view. Overall, doing so should be pretty easy from what I've seen online but when I try it, my map doesn't show any markers, clusters or anything. My code is below:
function calculateCenter() {
center = map.getCenter()
}
let center
let worldCenter = new window.google.maps.LatLng(32.249974, 5.800781)
let mapOptions = {
center: worldCenter,
zoom: 3,
scrollwheel: false
}
let map = new google.maps.Map(document.querySelector('.banner--map--parks'), mapOptions)
let infoWindow = new google.maps.InfoWindow()
let markers = []
map.addListener('idle', _ => {
calculateCenter()
})
fetch('/api/parks.json')
.then(response => response.json())
.then(data => {
data.forEach(park => {
if (park.lat && park.lng) {
let title = park.name
let position = new google.maps.LatLng(park.lat, park.lng)
let icon = 'https://raw.githubusercontent.com/Concept211/Google-Maps-Markers/master/images/marker_blue.png'
let marker = new google.maps.Marker({
position: position
title: title,
icon: icon,
// map: map
})
let parkIWContent = '<div class="iw">' + park.name + '</div>'
marker.addListener('click', (function(marker, parkIWContent) {
return function() {
infoWindow.setContent(parkIWContent);
infoWindow.open(map, marker);
// map.panTo(new google.maps.LatLng(park.lat, park.lng))
};
})(marker, parkIWContent))
markers.push(marker)
}
})
})
console.log(markers)
let markerCluster = new MarkerClusterer(map, markers)
map.addListener("click", _ => {
infoWindow.close()
})
As you can see, at one point I did output the array of markers and they do all show up in the console but still fail to show on the map.
Can anyone spot any issues with the code that may be causing this?
Thanks
I've actually managed to solve this. I needed to move the Marker Clusterer inside the .then in order for it to work.
I am trying to clean up google maps polylines once I render other ones. It works with the markers but with the polylines they stayed. I check google maps api google maps api
and couldn't make it work. It looks for me I am doing something wrong in the order but I have tried many ways and I can't find the solution.
function addMarkers(markerPosition: any, id?: number) {
// Creating markers
const position = { lat: markerPosition._latitude, lng: markerPosition._longitude };
marker = map && new window.google.maps.Marker({
map,
position,
id,
});
// Add listener to markers
marker.addListener('click', () => {
dispatch(getStop(id));
});
// Creating poliLine route
pathToRender.push(position);
// Focus on the markers
loc = map && new window.google.maps.LatLng(marker.position.lat(), marker.position.lng());
bounds.extend(loc);
return markers.push(marker);
}
useEffect(() => {
setStopInfo(stop.stop);
// stop.stop.userName !== '' && setPopUp(stop.stop);
}, [stop]);
function setPolyLine(pathRout: any) {
routePath = new window.google.maps.Polyline({
path: pathToRender,
geodesic: true,
strokeColor: '#FF0000',
strokeOpacity: 1.0,
strokeWeight: 2,
});
routePath.setMap(pathRout);
}
function setMapOnAll(mapToRender: any) {
for (let i = 0; i < markers.length; i += 1) {
markers[i].setMap(mapToRender);
}
map.fitBounds(bounds);
map.panToBounds(bounds);
}
function clearMarkers() {
setMapOnAll(null);
setPolyLine(null);
}
function markersAdministration(routeChoose: number) {
const route = showRoutes[routeChoose];
setMapConfig({ center: { lat: 40, lng: 10 }, zoom: 5, disableDefaultUI: false });
// Clear Markers
clearMarkers();
markers = [];
pathToRender = [];
// Add stops, destination and origin to the markers
addMarkers(route.origin.point);
route.stops && route.stops.map((routeStop: IStops) => addMarkers(routeStop.point, routeStop.id));
addMarkers(route.destination.point);
// Setting up markers and lines layers
setMapOnAll(map);
setPolyLine(map);
}
Thank you for the help.
I solve it, I don't know why it didn't work before but it is working now.
function setMapOnAll(mapToRender: any) {
// Create Markers
for (let i = 0; i < markers.length; i += 1) {
markers[i].setMap(mapToRender);
}
// Create Polyline
routePath = new window.google.maps.Polyline({
path: pathToRender,
geodesic: true,
strokeColor: '#FF0000',
strokeOpacity: 1.0,
strokeWeight: 2,
});
routePath.setMap(mapToRender);
// Map focus on Bounds
map.fitBounds(bounds);
map.panToBounds(bounds);
}
function clearMarkers() {
routePath && routePath.setMap(null);
setMapOnAll(null);
}
I'm trying to find a solution for changing the marker icon when the marker is tapped, in order to give visual feedback. Still, I haven't figured out how to do this. The first thought was accessing the marker by index but since markers are arranged in a Set<Markers> there is no change to access it in a proper way. then it would be easy to just exchange the old marker by the new one. Is there a common way to do this?
Edit:
Using Java, there are options like. setIcon for markers. This is not the chase for Flutter.
like so:
marker.setIcon(BitmapDescriptorFactory.fromResource(R.drawable.ic_selected_user_mark_icon));
Happy coding :)
I had the same problem. Google Maps API doesn't have this option. I resolved it manually.
You can create a flag and change the icon Marker in the Marker's onTap and GoogleMap's onTap properties.
int markerFlag = null;
List<Marker> _allMarkers = [];
Marker's onTap:
onTap: () {
if (markerFlag != idMarker) {
setState(() {
if (markerFlag != null) {
_allMarkers[markerFlag] = _allMarkers[markerFlag].copyWith(iconParam: defaultMarkerIcon);
}
_allMarkers[idMarker] = _allMarkers[idMarker].copyWith(iconParam: selectedMarkerIcon);
markerFlag = idMarker;
});
}
}
GoogleMap's onTap:
onTap: (value){
if (markerFlag != null) {
setState(() {
_allMarkers[markerFlag] = _allMarkers[markerFlag].copyWith(iconParam: defaultMarkerIcon);
markerFlag = null;
});
}
}
//onTap Marker:
widget.markersStation.add(
Marker(
markerId: MarkerId(markerId),
position: latLng,
icon: iconMarkerStationGrey,
onTap: () {
setState(() {
for (var j = 0; j < widget.markersStation.length; j++) {
widget.markersStation[j] = widget.markersStation[j]
.copyWith(iconParam: iconMarkerStationGrey);
}
if (idMarkerSelected != int.parse(markerId)) {
widget.markersStation[int.parse(markerId)] = widget
.markersStation[int.parse(markerId)]
.copyWith(iconParam: iconMarkerStationSelected);
idMarkerSelected = int.parse(markerId);
}
});
),)
//onTap Map :
markers: widget.markersStation.toSet(),
onTap: (position) async {
_customInfoWindowController.hideInfoWindow!();
BitmapDescriptor iconMarkerStationGrey =
await BitmapDescriptor.fromAssetImage(
const ImageConfiguration(size: Size(24, 24)),
"icons/marker_station_gris_17677#2x.png");
if (idMarkerSelected > -1) {
setState(() {
widget.markersStation[idMarkerSelected] = widget
.markersStation[idMarkerSelected]
.copyWith(iconParam: iconMarkerStationGrey);
idMarkerSelected = -1;
});
}
},
So am I am using gmaps.js and the gmaps.js along with marker clusterer. In my case I may have multiple markers with the exact same location, but in reality represent different data. To overcome this I am trying to implement the solution found here on SO - in particular the solution by Nathan Colgate.
The idea is when max zoom has reached on a cluster it will execute the multiChoice function. I have this part working. What I cannot get to work is showing an infoWindow with that function.
My goal is to show an infoWindow on this cluster click to display information about each marker (particularly each marker's infoWindow content (this will have additional details specific to it).
JS :
//create the map
var map = new GMaps({
el: '#map_canvas_main',
lat: response.results[0].lat,
lng: response.results[0].lng,
zoom: 5,
maxZoom: 15,
panControl: false,
markerClusterer: function(map) {
markerCluster = new MarkerClusterer(map, [], {
title: 'Location Cluster',
maxZoom: 15
});
// onClick OVERRIDE
markerCluster.onClick = function(clickedClusterIcon) {
return multiChoice(clickedClusterIcon.cluster_);
}
return markerCluster;
}
});
//loop through array
for(var i = 0; i < response.results.length; i++)
{
//create marker image
var markerLoc = {
url: '/custom/plugins/gmaps/images/marker-red.png',
size: new google.maps.Size(24, 30), //size
origin: new google.maps.Point(0, 0), //origin point
anchor: google.maps.Point(9, 30) // offset point
};
//add marker
map.addMarker({
lat: response.results[i].lat,
lng: response.results[i].lng,
icon: markerLoc,
title: response.results[i].ip_address,
infoWindow: {
content: '<p>'+response.results[i].ip_address+'</p>'
//add more details later
}
});
}
//cluster function to do stuff
function multiChoice(clickedCluster)
{
//clusters markers
var markers = clickedCluster.getMarkers();
//console check
console.log(clickedCluster);
console.log(markers);
if (markers.length > 1)
{
//content of info window
var infowindow = new google.maps.InfoWindow({
content: ''+
'<p>'+markers.length+' = length</p>'+
'<p>testing blah blah</p>'
});
//show the window
//infowindow.open(??????????);
return false;
}
return true;
};
Finally figured this out playing around with it some more... it makes sense now, but didn't before. Here is the new 'display' function to be replaced by the one in the OP. Of course, there are a few other change needed yet... showing all clustered marker data in the new info window for example, but this is the gist of getting the window to work.
//cluster function to do stuff
function multiChoice(clickedCluster)
{
//clusters markers
var markers = clickedCluster.getMarkers();
if (markers.length > 1)
{
//create the info window
var infoWindow = new google.maps.InfoWindow({
content: ''+
'<p>'+markers.length+' = length</p>'+
'<p>testing blah blah</p>',
position: clickedCluster.center_
});
//display the infowindow
infoWindow.open(clickedCluster.map_);
return false;
}
return true;
};