Related
Every time the Map is loading i am calling 500ms after finished the loading process the this.map.invalidateSize()-Method to try to clean the map view up.
But also when I am loading this, the map still appears like this:
What do i have to do, that the map loads directly in my location or at least in the defined center with the defined zoom-Level?
Is there any event, that I can listen for on Angular/Leaflet, which indicates, that the map has loaded properly and also the loaded KML-Data is loaded?
EDIT
The following code is producing this error. Please take note, that i have removed unused content, which is not relevant for this problem (like adding a marker to the map).
ngOnInit() {
this.initMap();
}
processBaseLayers() {
this.layerControl = L.control
.layers(this.layerControl, null, {
position: 'bottomright',
})
.addTo(this.map);
let defaultLayerSet = false;
this.baseLayers.forEach((layer) => {
const baseLayer = new L.TileLayer(layer.serverURL, {
maxZoom: layer.maxZoom,
attribution: layer.attribution,
});
if (!defaultLayerSet) {
baseLayer.addTo(this.map);
defaultLayerSet = true;
}
this.layerControl.addBaseLayer(baseLayer, layer.name);
});
}
fixMapOccurences() {
setTimeout(() => { this.map.invalidateSize() }, 500)
}
async initMap() {
this.map = L.map('map', {
center: [46.947222222222, 7.4441666666667],
zoom: 12,
zoomControl: false,
});
this.map.whenReady(() => {
this.processBaseLayers();
setTimeout(async () => {
this.fixMapOccurences();
await this.loadKML();
}, 1000)
});
}
async loadKML() {
this.dataLayers.forEach((dataLayer) => {
const kmlLayer = omnivore.kml(dataLayer.serverURL).on('ready', () => {
kmlLayer.eachLayer((layer) => {
if (layer.feature.geometry.type === 'Point') {
// adding point here
} else if (layer.feature.geometry.type === 'GeometryCollection') {
// adding polyline here
}
});
});
});
}
I am attempting to use React to build a website with a Google map that redraws the Polylines after I change the state. I am using setState() to set the state correctly, and I can see the values change when I print the object from this.state in the console but my Polylines on map do not re-render, and change color.
This is the main component that I am using for the project it includes a map and a table, renderMap() is what actually generates the map and update_congestion_lines is what changes the state and I am hoping to trigger a re-render.
import React, { Component } from 'react';
import {Map, Marker, GoogleApiWrapper, InfoWindow, Polyline} from 'google-maps-react';
class HomepageMap extends Component{
static defaultProps = {
center: {
lat: 33.980530,
lng: -117.377020
},
zoom: 11,
style: {
width: '100%',
height: '100%'
}
};
constructor(props){
super(props);
this.state = {
error: null,
cctvs: [],
cctv_objects: [],
center: {
lat: 33.980530,
lng: -117.377020
},
style: {
width: '100%',
height: '100%'
},
zoom: 11,
showingInfoWindow : false,
selectedPlace: {},
activeMarker: {},
image_path : [],
};
this.update_congestion_lines = this.update_congestion_lines.bind(this);
this.grabColor = this.grabColor.bind(this);
this.handleClick = this.handleClick.bind(this);
}
componentDidMount() {
var url = "http://highwayanalytics.us/api/cctv?format=json&county=Riverside,San+Bernardino";
fetch(url)
.then(res => res.json())
.then(
(result) => {
var list = [];
for(var i = 0; i < result.length; i++){
var cctv = result[i];
if(cctv.image_url !== "Not Reported"){
list.push(cctv);
}
}
this.setState({
cctvs: list,
error: false
});
},
(error) => {
//console.log(error);
this.setState({
cctvs: [],
error: true
})
});
url = "http://highwayanalytics.us/api/graph?format=json&county=Riverside,San+Bernardino";
fetch(url)
.then(res => res.json())
.then(
(result) => {
var list = [];
for(var key in result){
if(result.hasOwnProperty(key)){
var val = result[key];
var i = 0;
if(key === "SR-60" || key === "I-10" || key === "SR-91" || key === "I-210"){
for(i=0;i < val.length;i++){
var prev_cctv = null;
if(i!== 0){
prev_cctv=val[i-1];
}
var next_cctv = null;
if(i !== (val.length-1)){
next_cctv = val[i+1];
//Calc distance
var prev_lat_midpoint = null;
var prev_long_midpoint = null;
var next_lat_midpoint = null;
var next_long_midpoint = null;
var temp = null;
if(prev_cctv !== null){
if(prev_cctv.latitude > val[i].latitude){
temp = Math.abs(prev_cctv.latitude-val[i].latitude)/2;
prev_lat_midpoint = val[i].latitude + temp;
}
else{
temp = Math.abs(prev_cctv.latitude-val[i].latitude)/2;
prev_lat_midpoint = val[i].latitude - temp;
}
if(prev_cctv.longitude > val[i].longitude){
temp = Math.abs(prev_cctv.longitude-val[i].longitude)/2;
prev_long_midpoint = val[i].longitude + temp;
}
else{
temp = Math.abs(prev_cctv.longitude-val[i].longitude)/2;
prev_long_midpoint = val[i].longitude - temp;
}
}
if(next_cctv !== null){
if(next_cctv.latitude > val[i].latitude){
temp = Math.abs(next_cctv.latitude-val[i].latitude)/2;
next_lat_midpoint = val[i].latitude + temp;
}
else{
temp = Math.abs(next_cctv.latitude-val[i].latitude)/2;
next_lat_midpoint = val[i].latitude - temp;
}
if(next_cctv.longitude > val[i].longitude){
temp = Math.abs(next_cctv.longitude-val[i].longitude)/2;
next_long_midpoint = val[i].longitude + temp;
}
else{
temp = Math.abs(next_cctv.longitude-val[i].longitude)/2;
next_long_midpoint = val[i].longitude - temp;
}
}
var object = {
"cctv": val[i],
"cctv_id":val[i].cctv_id,
"prev_cctv": prev_cctv,
"next_cctv": next_cctv,
"prev_lat_midpoint": prev_lat_midpoint,
"prev_long_midpoint": prev_long_midpoint,
"next_lat_midpoint": next_lat_midpoint,
"next_long_midpoint": next_long_midpoint,
"car_count": null
}
list.push(object);
}
}
}
else if( key === "I-15" || key === "I-215"){
for(i=0;i < val.length;i++){
var prev_cctv = null;https://reactjs.org/docs/state-and-lifecycle.html
if(i!== 0){
prev_cctv=val[i-1];
}
var next_cctv = null;
if(i !== (val.length-1)){
next_cctv = val[i+1];
//Calc distance
var prev_lat_midpoint = null;
var prev_long_midpoint = null;
var next_lat_midpoint = null;
var next_long_midpoint = null;
var temp = null;
if(prev_cctv !== null){
if(prev_cctv.latitude > val[i].latitude){
temp = Math.abs(prev_cctv.latitude-val[i].latitude)/2;
prev_lat_midpoint = val[i].latitude + temp;
}
else{
temp = Math.abs(prev_cctv.latitude-val[i].latitude)/2;
prev_lat_midpoint = val[i].latitude - temp;
}
if(prev_cctv.longitude > val[i].longitude){
temp = Math.abs(prev_cctv.longitude-val[i].longitude)/2;
prev_long_midpoint = val[i].longitude + temp;
}
else{
temp = Math.abs(prev_cctv.longitude-val[i].longitude)/2;
prev_long_midpoint = val[i].longitude - temp;
}
}
if(next_cctv !== null){
if(next_cctv.latitude > val[i].latitude){
temp = Math.abs(next_cctv.latitude-val[i].latitude)/2;
next_lat_midpoint = val[i].latitude + temp;
}
else{
temp = Math.abs(next_cctv.latitude-val[i].latitude)/2;
next_lat_midpoint = val[i].latitude - temp;
}
if(next_cctv.longitude > val[i].longitude){
temp = Math.abs(next_cctv.longitude-val[i].longitude)/2;
next_long_midpoint = val[i].longitude + temp;
}
else{
temp = Math.abs(next_cctv.longitude-val[i].longitude)/2;
next_long_midpoint = val[i].longitude - temp;
}
}
object = {
"cctv": val[i],
"cctv_id": val[i].cctv_id,
"prev_cctv": prev_cctv,
"next_cctv": next_cctv,
"prev_lat_midpoint": prev_lat_midpoint,
"prev_long_midpoint": prev_long_midpoint,
"next_lat_midpoint": next_lat_midpoint,
"next_long_midpoint": next_long_midpoint,
"car_count": null
}
list.push(object);
}
}
}
else{
continue;
}
}
}
this.setState({
cctv_objects: list,
error: false
});
},
(error) => {
//console.log(error);
this.setState({
cctvs: [],
error: true
})
});
//Used for updating congestion lines, every 10 seconds
// this.intervalID = setInterval(
// ()=> this.update_congestion_lines(),
// 10000
// );
}
componentWillUnmount(){
clearInterval(this.intervalID);
}
update_congestion_lines(){
//Update Congestions Lines
console.log("Updating Congestion lines");
var cctv_objects_dup = Array.from(this.state.cctv_objects);
//Go through all cameras
for(var i = 0; i < cctv_objects_dup.length;i++){
var target_url = "http://highwayanalytics.us/api/vehicle/?cctv="+cctv_objects_dup[i].cctv_id+"&format=json";
var target_photo_id = null;
if(cctv_objects_dup[i].cctv_id !== undefined){
fetch(target_url)
.then(res => res.json())
.then(
(result) => {
if(result !== undefined){
if(result.results !== undefined){
if(result.results[0] !== undefined){
//Find most recent photo id on specific camera
target_photo_id = result.results[0].photo;
target_url = "http://highwayanalytics.us/api/vehicle/?photo="+target_photo_id+"&format=json";
fetch(target_url)
.then( res => res.json())
.then(
(result) => {
//assign that camera the result of photo count
for(let index = 0; index < cctv_objects_dup.length; index++){
if(cctv_objects_dup[index] === undefined){
console.log("undefined");
continue;
}
if(cctv_objects_dup[index].cctv_id === result.results[0].cctv){
cctv_objects_dup[index].car_count = result.count;
break;
}
}
},
(error) =>{
console.log("Error with using target_photo_id");
}
);
}
}
}
},
(error) => {
console.log("Error updating Congestion");
}
);
}
}
//update cctv objects with new car counts
this.setState({
cctv_objects: cctv_objects_dup
});
//console.log("Lines Done Updating",this.state.cctv_objects[0].car_count);
//console.log("here is cctv_objects",this.state.cctv_objects);
}
onMarkerClick = (props, marker) => {
var latest_image = "http://highwayanalytics.us/api/search/?search=" + props.name;
var path = [];
fetch(latest_image)
.then(res => res.json())
.then(
(result) => {
//console.log(result.results[0].file_name)
path.push(result.results[0].file_name)
this.setState({
activeMarker: marker,
selectedPlace: props,
showingInfoWindow: true,
image_path : path,
});
}
)
};
onMouseoverMarker= (props, marker, e) => {
// this.setState({
// activeMarker: marker,
// selectedPlace: props,
// showingInfoWindow: true
// })
// console.log(this.state.showingInfoWindow)
};
onMouseoutMarker= (props, marker, e) => {
// this.setState({
// activeMarker: null,
// showingInfoWindow: false
// })
// console.log(this.state.showingInfoWindow)
};
onInfoWindowClose = () =>
this.setState({
activeMarker: null,
showingInfoWindow: false
});
onMapClicked = () => {
if (this.state.showingInfoWindow){
this.setState({
activeMarker: null,
showingInfoWindow: false,
image_path: null,
})
console.log(this.state.showingInfoWindow)
}
};
grabColor = (car_count) =>{
console.log("Car Count", car_count);
if(car_count === null){
return 'green';
}
else{
return 'red';
}
}
renderMap(){
var icon_image = process.env.PUBLIC_URL + '/camera_icon2.png';
var cctvs = this.state.cctvs.map(
(d) =>
<Marker
icon={icon_image}
name = {d.id}
onClick = {this.onMarkerClick}
onMouseover={this.onMouseoverMarker}
onMouseout={this.onMouseoutMarker}
position = { {lat: d.latitude, lng: d.longitude} }
lat = {d.latitude}
long = {d.longitude}
image_url = {d.image_url}
route = {d.route}
/>
);
var prev_congestion_lines = this.state.cctv_objects.map(
(object)=>(
//prev_polyline
<Polyline
key= {object.cctv.latitude.toString() + object.cctv.longitude.toString()}
path={[
{ lat: object.prev_lat_midpoint, lng: object.prev_long_midpoint},
{ lat: object.cctv.latitude, lng: object.cctv.longitude},
]}
options={{
strokeColor: this.grabColor(object.car_count),
strokeOpacity: 0.75,
strokeWeight: 10,
icons: [{
offset: '0',
repeat: '10px'}],
}}
/>
)
);
var next_congestion_lines = this.state.cctv_objects.map(
(object)=>(
//prev_polyline
<Polyline
key = {object.cctv_id.toString() + object.cctv.latitude.toString() + object.cctv.longitude.toString()}
path={[
{ lat: object.cctv.latitude, lng: object.cctv.longitude},
{ lat: object.next_lat_midpoint, lng: object.next_long_midpoint},
]}
options={{
strokeColor: this.grabColor(object.car_count),
strokeOpacity: 0.75,
strokeWeight: 10,
icons: [{
offset: '0',
repeat: '10px'}],
}}
/>
)
);
return (
<div style={{ height: '92vh', width: '100%' }}>
<Map
google={this.props.google}
zoom={this.props.zoom}
style={this.props.style}
initialCenter={this.props.center}
onClick={this.onMapClicked}
>
{cctvs}
{prev_congestion_lines}
{next_congestion_lines}
</Map>
</div>
);
}
renderTable(){
var content = []
var path = "http://highwayanalytics.us/image/" + this.state.image_path;
if (this.state.showingInfoWindow !== false) {
content.push(
<div className="row" style={{'padding': '35px'}}>
<h2> Marker Information</h2>
<img
src = {path}
style={{
width: '380px'
}}
/>
<div>
<span> Lat : {this.state.selectedPlace.lat} Long: {this.state.selectedPlace.long}</span>
<p> Route : {this.state.selectedPlace.route} Marker_Id : {this.state.selectedPlace.name}</p>
</div>
</div>
);
}else{
content.push(
<div className="row" style={{'padding': '35px'}}>
<h2> Marker Information</h2>
</div>
);
}
return content;
}
handleClick(){
this.update_congestion_lines();
console.log(this.state.cctv_objects);
this.forceUpdate();
}
render(){
var map = this.renderMap();
var table = this.renderTable();
return(
<div className="row">
<div className="col-9">
{map}
</div>
<div className="col-3">
{table}
<button onClick={this.handleClick}> Update Congestion Lines</button>
</div>
</div>
);
}
}
export default GoogleApiWrapper({
apiKey: process.env.REACT_APP_GOOGLE_MAP_KEY
})(HomepageMap);
renderMap() takes care of generating the map, markers, and the polylines. The color of the polyline should change based on the car_count. As you can see am logging the car count,the car count should be null initially but is not null after the state is set, even though console.log tells me the car_count is not null, the color of the line does not change.
grabColor = (car_count) =>{
console.log("Car Count", car_count);
if(car_count === null){
return 'green';
}
else{
return 'red';
}
}
renderMap(){
var icon_image = process.env.PUBLIC_URL + '/camera_icon2.png';
var cctvs = this.state.cctvs.map(
(d) =>
<Marker
icon={icon_image}
name = {d.id}
onClick = {this.onMarkerClick}
onMouseover={this.onMouseoverMarker}
onMouseout={this.onMouseoutMarker}
position = { {lat: d.latitude, lng: d.longitude} }
lat = {d.latitude}
long = {d.longitude}
image_url = {d.image_url}
route = {d.route}
/>
);
var prev_congestion_lines = this.state.cctv_objects.map(
(object)=>(
//prev_polyline
<Polyline
key= {object.cctv.latitude.toString() + object.cctv.longitude.toString()}
path={[
{ lat: object.prev_lat_midpoint, lng: object.prev_long_midpoint},
{ lat: object.cctv.latitude, lng: object.cctv.longitude},
]}
options={{
strokeColor: this.grabColor(object.car_count),
strokeOpacity: 0.75,
strokeWeight: 10,
icons: [{
offset: '0',
repeat: '10px'}],
}}
/>
)
);
var next_congestion_lines = this.state.cctv_objects.map(
(object)=>(
<Polyline
key = {object.cctv_id.toString() + object.cctv.latitude.toString() + object.cctv.longitude.toString()}
path={[
{ lat: object.cctv.latitude, lng: object.cctv.longitude},
{ lat: object.next_lat_midpoint, lng: object.next_long_midpoint},
]}
options={{
strokeColor: this.grabColor(object.car_count),
strokeOpacity: 0.75,
strokeWeight: 10,
icons: [{
offset: '0',
repeat: '10px'}],
}}
/>
)
);
Here is what I get on the console and what the map looks like.
If anyone has an explanation of why the polylines are not being re-rendered after the car_count changes, I would be extremely grateful.
Edit: I have added to rest of the function of the first code block, and added a key to the next_congestion line as noted in the comments.
i implemented leaflet and leafletDraw in my Angular6 app, it works fine and i can trigger the create event and add the polygon to my map, but when i try to delete or edit my polygon i can't find which shape is deleted or edited:
ngOnInit() {
const myMap = this.mapElement.nativeElement;
const map = L.map(myMap).setView([35.6892, 51.3890], 5);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: 'Data © OpenStreetMap',
maxZoom: 18,
}).addTo(map);
const editableLayers = new L.FeatureGroup();
map.addLayer(editableLayers);
if (this.type === 'marker') {
this.marker = MarkerOptions;
if (this.data) {
L.marker(this.data, MarkerOptions).addTo(editableLayers).bindPopup('I am popup');
}
} else if (this.type === 'polygon') {
this.polygon = {
allowIntersection: false,
drawError: {
message: '<strong>Oh snap!<strong> you can\'t draw that!'
},
shapeOptions: {}
};
if (this.data) {
L.polygon(this.data).addTo(editableLayers);
}
}
const drawPluginOptions: LeafletControls.Control.DrawConstructorOptions = {
position: 'topright',
draw: {
polyline: false,
polygon: this.polygon,
circle: false,
rectangle: false,
circlemarker: false,
marker: this.marker
},
edit: {
featureGroup: editableLayers,
remove: {},
edit: {
selectedPathOptions: {
stroke: false ,
color : '#e10010',
weight : 500
}
}
}
};
const drawControl = new L.Control.Draw(drawPluginOptions);
map.addControl(drawControl);
map.once(L.Draw.Event.CREATED, (e: any) => {
console.log('lia e' , e);
this.layer = e.layer;
// if (type === 'marker') {
// layer.bindPopup('A popup!');
// }
editableLayers.addLayer(this.layer);
});
map.on('draw:edited', (e: any) => {
console.log('lia edit' , e , this.layer); //unable to trigger which shape is.
});
map.on('draw:deleted', (e: any) => {
console.log('lia delete' , e ); //unable to trigger which shape is.
console.log(this.layer);
});
}
The draw:edited and draw:deleted events pass you a LayerGroup which contains the layers that were edited/deleted.
map.on('draw:edited', (e: any) => {
var editedlayers = e.layers;
editedlayers.eachLayer(function(layer) { // Do something with the edited layer
});
});
Am using vue and have installed the vue-mapbox component located here: https://soal.github.io/vue-mapbox/#/quickstart
I have updated the js and css to the latest versions also that gets added to the index.html:
<!-- Mapbox GL CSS -->
<link href="https://api.tiles.mapbox.com/mapbox-gl-js/v0.51.0/mapbox-gl.css" rel="stylesheet" />
<!-- Mapbox GL JS -->
<script src="https://api.tiles.mapbox.com/mapbox-gl-js/v0.51.0/mapbox-gl.js"></script>
I am trying to utilize this component to set the default view of the map bounds using either center or bounds or fitBounds to a list of Lng,Lat coordinates. So, basically, how to plug in lng,lat coordinates and have the map default to centering these coordinates inside of the container?
Here's a Component I created, called Map in vue to output the mapbox using the component vue-mapbox listed above:
<template>
<b-row id="map" class="d-flex justify-content-center align-items-center my-2">
<b-col cols="24" id="map-holder" v-bind:class="getMapType">
<mgl-map
id="map-obj"
:accessToken="accessToken"
:mapStyle.sync="mapStyle"
:zoom="zoom"
:center="center"
container="map-holder"
:interactive="interactive"
#load="loadMap"
ref="mapbox" />
</b-col>
</b-row>
</template>
<script>
import { MglMap } from 'vue-mapbox'
export default {
components: {
MglMap
},
data () {
return {
accessToken: 'pk.eyJ1Ijoic29sb2dob3N0IiwiYSI6ImNqb2htbmpwNjA0aG8zcWxjc3IzOGI1ejcifQ.nGL4NwbJYffJpjOiBL-Zpg',
mapStyle: 'mapbox://styles/mapbox/streets-v9', // options: basic-v9, streets-v9, bright-v9, light-v9, dark-v9, satellite-v9
zoom: 9,
map: {}, // Holds the Map...
fitBounds: [[-79, 43], [-73, 45]]
}
},
props: {
interactive: {
default: true
},
resizeMap: {
default: false
},
mapType: {
default: ''
},
center: {
type: Array,
default: function () { return [4.899, 52.372] }
}
},
computed: {
getMapType () {
let classes = 'inner-map'
if (this.mapType !== '') {
classes += ' map-' + this.mapType
}
return classes
}
},
watch: {
resizeMap (val) {
if (val) {
this.$nextTick(() => this.$refs.mapbox.resize())
}
},
fitBounds (val) {
if (this.fitBounds.length) {
this.MoveMapCoords()
}
}
},
methods: {
loadMap () {
if (this.map === null) {
this.map = event.map // store the map object in here...
}
},
MoveMapCoords () {
this.$refs.mapbox.fitBounds(this.fitBounds)
}
}
}
</script>
<style lang="scss" scoped>
#import '../../styles/custom.scss';
#map {
#map-obj {
text-align: justify;
width: 100%;
}
#map-holder {
&.map-modal {
#map-obj {
height: 340px;
}
}
&.map-large {
#map-obj {
height: 500px;
}
}
}
.mapboxgl-map {
border: 2px solid lightgray;
}
}
</style>
So, I'm trying to use fitBounds method here to get the map to initialize centered over 2 Lng,Lat coordinates here: [[-79, 43], [-73, 45]]
How to do this exactly? Ok, I think I might have an error in my code a bit, so I think the fitBounds should look something like this instead:
fitBounds: () => {
return { bounds: [[-79, 43], [-73, 45]] }
}
In any case, having the most difficult time setting the initial location of the mapbox to be centered over 2 or more coordinates. Anyone do this successfully yet?
Ok, so I wound up creating a filter to add space to the bbox like so:
Vue.filter('addSpaceToBBoxBounds', function (value) {
if (value && value.length) {
var boxArea = []
for (var b = 0, len = value.length; b < len; b++) {
boxArea.push(b > 1 ? value[b] + 2 : value[b] - 2)
}
return boxArea
}
return value
})
This looks to be good enough for now. Than just use it like so:
let line = turf.lineString(this.markers)
mapOptions['bounds'] = this.$options.filters.addSpaceToBBoxBounds(turf.bbox(line))
return mapOptions
setting the initial location of the map to be centered over 2 or
more coordinates
You could use Turf.js to calculate the bounding box of all point features and initialize the map with this bbox using the bounds map option:
http://turfjs.org/docs#bbox
https://www.mapbox.com/mapbox-gl-js/api/#map
I created a few simple functions to calculate a bounding box which contains the most southwestern and most northeastern corners of the given [lng, lat] pairs (markers). You can then use Mapbox GL JS map.fitBounds(bounds, options?) function to zoom the map to the set of markers.
Always keep in mind:
lng (lon): longitude (London = 0, Bern = 7.45, New York = -74)
→ the lower, the more western
lat: latitude (Equator = 0, Bern = 46.95, Capetown = -33.9)
→ the lower, the more southern
getSWCoordinates(coordinatesCollection) {
const lowestLng = Math.min(
...coordinatesCollection.map((coordinates) => coordinates[0])
);
const lowestLat = Math.min(
...coordinatesCollection.map((coordinates) => coordinates[1])
);
return [lowestLng, lowestLat];
}
getNECoordinates(coordinatesCollection) {
const highestLng = Math.max(
...coordinatesCollection.map((coordinates) => coordinates[0])
);
const highestLat = Math.max(
...coordinatesCollection.map((coordinates) => coordinates[1])
);
return [highestLng, highestLat];
}
calcBoundsFromCoordinates(coordinatesCollection) {
return [
getSWCoordinates(coordinatesCollection),
getNECoordinates(coordinatesCollection),
];
}
To use the function, you can just call calcBoundsFromCoordinates and enter an array containing all your markers coordinates:
calcBoundsFromCoordinates([
[8.03287, 46.62789],
[7.53077, 46.63439],
[7.57724, 46.63914],
[7.76408, 46.55193],
[7.74324, 46.7384]
])
// returns [[7.53077, 46.55193], [8.03287, 46.7384]]
Overall it might even be easier to use Mapbox' mapboxgl.LngLatBounds() function.
As mentioned in the answer from jscastro in Scale MapBox GL map to fit set of markers you can use it like this:
const bounds = mapMarkers.reduce(function (bounds, coord) {
return bounds.extend(coord);
}, new mapboxgl.LngLatBounds(mapMarkers[0], mapMarkers[0]));
And then just call
map.fitBounds(bounds, {
padding: { top: 75, bottom: 30, left: 90, right: 90 },
});
If you don't want to use yet another library for this task, I came up with a simple way to get the bounding box, here is a simplified vue component.
Also be careful when storing your map object on a vue component, you shouldn't make it reactive as it breaks mapboxgl to do so
import mapboxgl from "mapbox-gl";
export default {
data() {
return {
points: [
{
lat: 43.775433,
lng: -0.434319
},
{
lat: 44.775433,
lng: 0.564319
},
// Etc...
]
}
},
computed: {
boundingBox() {
if (!Array.isArray(this.points) || !this.points.length) {
return undefined;
}
let w, s, e, n;
// Calculate the bounding box with a simple min, max of all latitudes and longitudes
this.points.forEach((point) => {
if (w === undefined) {
n = s = point.lat;
w = e = point.lng;
}
if (point.lat > n) {
n = point.lat;
} else if (point.lat < s) {
s = point.lat;
}
if (point.lng > e) {
e = point.lng;
} else if (point.lng < w) {
w = point.lng;
}
});
return [
[w, s],
[e, n]
]
},
},
watch: {
// Automatically fit to bounding box when it changes
boundingBox(bb) {
if (bb !== undefined) {
const cb = () => {
this.$options.map.fitBounds(bb, {padding: 20});
};
if (!this.$options.map) {
this.$once('map-loaded', cb);
} else {
cb();
}
}
},
// Watch the points to add the markers
points: {
immediate: true, // Run handler on mount (not needed if you fetch the array of points after it's mounted)
handler(points, prevPoints) {
// Remove the previous markers
if (Array.isArray(prevPoints)) {
prevPoints.forEach((point) => {
point.marker.remove();
});
}
//Add the new markers
const cb = () => {
points.forEach((point) => {
// create a HTML element for each feature
const el = document.createElement('div');
el.className = 'marker';
el.addEventListener('click', () => {
// Marker clicked
});
el.addEventListener('mouseenter', () => {
point.hover = true;
});
el.addEventListener('mouseleave', () => {
point.hover = false;
});
// make a marker for each point and add to the map
point.marker = new mapboxgl.Marker(el)
.setLngLat([point.lng, point.lat])
.addTo(this.$options.map);
});
};
if (!this.$options.map) {
this.$once('map-loaded', cb);
} else {
cb();
}
}
}
},
map: null, // This is important to store the map without reactivity
methods: {
mapLoaded(map) {
this.$options.map = map;
this.$emit('map-loaded');
},
},
}
It should work fine as long as your points aren't in the middle of the pacific juggling between 180° and -180° of longitude, if they are, simply adding a check to invert east and west in the return of the bounding box should do the trick
I'm using angular google maps API. My problem is that when my geolocation is turned off and I use the search box to search for generic, not address-bound things or stores like "pizza" or "ikea" the map shows its results in the US by default.
I want to be able to choose my own default location to use when the geolocation is turned off. But i'm not sure how to do this.
This is my code
//SEARCH FEATURES
$scope.searchOpen = false;
$scope.searchModel = {}
//Add and remove css classes on search bar to show and hide the searchbar
$scope.toggleSearch = function () {
$scope.searchModel.searchTerm = null;
var searchFieldInput = document.getElementById('pac-input')
if (searchFieldInput.classList.contains('searchactive')) {
searchFieldInput.classList.remove('searchactive')
searchFieldInput.classList.remove('pac-container')
} else {
searchFieldInput.classList.add('searchactive')
}
}
$scope.$watch("searchModel.searchTerm", function (searchText) {
$scope.filteredMarkers = $filter("filter")($scope.specialMarker, searchText);
if (!$scope.filteredMarkers) {
return;
}
});
$scope.searchbox = {
template: 'searchbox.tpl.html',
position: 'top-left',
options: {
bounds: {}
},
events: {
places_changed: function (searchBox) {
$scope.repositionAllowed = false;
places = searchBox.getPlaces()
if (places.length == 0) {
return;
}
// For each place, get the icon, place name, and location.
newMarkers = [];
var bounds = new google.maps.LatLngBounds();
for (var i = 0, place; place = places[i]; i++) {
// Create a marker for each place.
console.log(place.place_id)
var marker = $scope.createMarker({
id: 's' + i,
title: place.name,
address: place.formatted_address,
lat: place.geometry.location.lat(),
lng: place.geometry.location.lng(),
place: place,
pointType: "search"
})
newMarkers.push(marker);
bounds.extend(place.geometry.location);
}
$scope.map.bounds = {
northeast: {
latitude: bounds.getNorthEast().lat(),
longitude: bounds.getNorthEast().lng()
},
southwest: {
latitude: bounds.getSouthWest().lat(),
longitude: bounds.getSouthWest().lng()
}
}
$scope.map.zoom = 13;
$scope.map.markers = newMarkers;
document.getElementById("pac-input").blur();
}
}
}
$scope.defaultBounds = new google.maps.LatLngBounds(
new google.maps.LatLng(37.9839, 22.7294),
new google.maps.LatLng(38.9839, 24.31715));
$scope.map.bounds = {
northeast: {
latitude: $scope.defaultBounds.getNorthEast().lat(),
longitude: $scope.defaultBounds.getNorthEast().lng()
},
southwest: {
latitude: $scope.defaultBounds.getSouthWest().lat(),
longitude: -$scope.defaultBounds.getSouthWest().lng()
}
}
$scope.searchbox.options.bounds = new google.maps.LatLngBounds($scope.defaultBounds.getNorthEast(), $scope.defaultBounds.getSouthWest());
});
thanks