To set the map zoom level to include all the location markers, I have tried two options as suggested in this post.
Here's what I did:
export class LocationSelectionComponent implements OnInit, AfterViewInit {
#ViewChild('AgmMap') agmMap: AgmMap;
ngAfterViewInit() {
console.log(this.agmMap);
this.agmMap.mapReady.subscribe(map => {
const bounds: LatLngBounds = new window['google'].maps.LatLngBounds();
for (const mm of this.mapMarkers) {
if (mm.latitude !== this.currentLocationLatitude && mm.longitude !== this.currentLocationLongitude) {
bounds.extend(new window['google'].maps.LatLng(mm.latitude, mm.longitude));
}
}
map.fitBounds(bounds);
});
}
}
Note that this.mapMarkers is an array which contains the coordinates for the map markers. These are populated in ngOnInit().
As mentioned in the comment of the above post, I've also tried the following:
onMapReady(map: AgmMap) {
const bounds: LatLngBounds = new window['google'].maps.LatLngBounds();
for (const mm of this.mapMarkers) {
if (mm.latitude !== this.currentLocationLatitude && mm.longitude !== this.currentLocationLongitude) {
bounds.extend(new window['google'].maps.LatLng(mm.latitude, mm.longitude));
}
}
// #ts-ignore
map.fitBounds(bounds);
}
then in HTML:
<agm-map #AgmMap [latitude]="latitude" [longitude]="longitude"
[fullscreenControl]="true" [mapTypeControl]="true" (mapReady)="onMapReady($event)">
<agm-marker *ngFor="let m of mapMarkers; let i = index"
[latitude]="m.latitude"
[longitude]="m.longitude"
[title]="m.title"
[iconUrl]="m.iconUrl"
[animation]="m.animation"
(markerClick)="clickedMarker(m.label)">
</agm-marker>
</agm-map>
But in both instances, I don't get the map zoomed out as I expect. The reason is, when I debug the code, the mapMarkers array is empty in both instances. How do I fix this?
Add [fitBounds]="true" to <agm-map>
Add [agmFitBounds]="true" to <agm-marker>
Remove [usePanning]="true" from <agm-map>
For more usability Add clustering:
install this module and follow the instructions
Related
I am using the Autodesk Viewer with the Edit2D extension and working on labels. The strange thing is that labels seem to leave a 'residue' when the polygon is hidden.
I have several polygons on the layer and the ability for the user to hide them. When they do that, this code does the hiding:
export function setRegionVisibility(
editor: Autodesk.Extensions.Edit2D,
region: RegionData,
geometry: SpaceGeometry,
visible: boolean
): void {
const shape = editor.defaultContext.layer.shapes.find(
(s) => s.id === region.itemIds[0]
);
if (shape == null) {
return;
}
// #ts-ignore
shape.visible = visible;
editor.defaultContext.layer.update();
return;
}
However, when a polygon is hidden, the label that was for it remains on the view. When I re-show the shape it gets a NEW label which moves with it, but the old one remains on the view and just stays in a static location regardless of the zooming or panning of the view.
I tried to manually hide it or remove it but nothing seems to work. This is how I tried to do that:
export function setRegionVisibility(
editor: Autodesk.Extensions.Edit2D,
region: RegionData,
geometry: SpaceGeometry,
// #ts-ignore
tagRule: Autodesk.Edit2D.ShapeLabelRule | undefined,
visible: boolean
): void {
const shape = editor.defaultContext.layer.shapes.find(
(s) => s.id === region.itemIds[0]
);
if (shape == null) {
return;
}
// #ts-ignore
shape.visible = visible;
let label: any;
for (let labelsKey in tagRule.labels) {
if (labelsKey === shape.id.toString()) {
label = tagRule.labels[labelsKey];
}
console.log(tagRule.labels);
}
if (label != null) {
label.visible = visible;
console.log(label);
label.dtor();
label.update();
label.layer.update();
}
editor.defaultContext.layer.update();
return;
}
Any idea how to make that ghost go away?
I am trying to add google maps to my vuejs application following this tutorial here: https://markus.oberlehner.net/blog/using-the-google-maps-api-with-vue/
I have a div with id="maps" as well as ref="mapsection". I tried binding maps instance with the div with both document.getElementById as well as this.ref but I get the null/undefined error. Can someone please advise what I am doing wrong? I see the div created when I go I to inspect mode.
I have tried both of the following where "mapsection" is the ref and "map" is the id for the div.
const map = new google.maps.Map(this.refs.mapsection);
const map = new google.maps.Map(document.getElementById('map'));
View code:
<el-row>
<div ref="mapsection" id="map" style="width:100%;height:400px">
</div>
</el-row>
Script code:
async mounted() {
try {
console.log(document.getElementById('map')); //returns null
const google = await gmapsInit();
const geocoder = new google.maps.Geocoder();
const map = new google.maps.Map(document.getElementById('map')); //tried this.refs.mapsection as well.
const locations = [{
position: {
lat: 48.160910,
lng: 16.383330
}
}]
geocoder.geocode({ address: 'Austria' }, (results, status) => {
if (status !== 'OK' || !results[0]) {
throw new Error(status);
}
map.setCenter(results[0].geometry.location);
map.fitBounds(results[0].geometry.viewport);
const markers = locations.map(x => new google.maps.Marker({ ...x, map }));
});
} catch (error) {
console.error(error);
}
}
Errors I am getting:
with this.refs.mapsection > TypeError: Cannot read property 'mapsection' of undefined
with document.getElementById('maps') > TypeError: Cannot read property 'firstChild' of null
The referenced tutorial does not use el-row. Your problem has nothing to do with google maps.
To debug try one of these approaches.
put all of mounted in an async $nextTick to ensure render.
move your code out of el-row, el-table, etc, and into a div to isolate the issue.
refs in loops is often an array, so when you finally get this working, it is something to consider.
I create tab on Ionic project. When i would access to Google map from another url Tab, it's not working but when i access it directly it works.
First the Ionic part:
The tab showing the map is:
Ionic calls refreshMap() when the user selects the tab.
refreshMap() is:
.controller('MainCtrl', function($scope) {
$scope.refreshMap = function() {
setTimeout(function () {
$scope.refreshMap_();
}, 1); //Need to execute it this way because the DOM may not be ready yet
};
$scope.refreshMap_ = function() {
var div = document.getElementById("map_canvas");
reattachMap(map,div);
};
})
I've implemented reattachMap() looking at the Map.init() method:
function reattachMap(map,div) {
if (!isDom(div)) {
console.log("div is not dom");
return map;
} else {
map.set("div", div);
while(div.parentNode) {
div.style.backgroundColor = 'rgba(0,0,0,0)';
div = div.parentNode;
}
return map;
}
}
function isDom(element) {
return !!element &&
typeof element === "object" &&
"getBoundingClientRect" in element;
}
And that's about it, now when the user switches back to the map tab, it will be there.
Please refer this.
(https://github.com/mapsplugin/cordova-plugin-googlemaps/issues/256/#issuecomment-59784091)
How can I force a new layer added to the map in Leaflet to be the first over the basemap?
I could not find a method to easily change the order of the layers, which is a very basic GIS feature. Am I missing something?
A Leaflet map consists of a collection of "Panes" whose view order is controlled using z-index. Each pane contains a collection of Layers The default pane display order is tiles->shadows->overlays->markers->popups. Like Etienne described, you can control the display order of Paths within the overlays pane by calling bringToFront() or bringToBack(). L.FeatureGroup also has these methods so you can change the order of groups of overlays at once if you need to.
If you want to change the display order of a whole pane then you just change the z-index of the pane using CSS.
If you want to add a new Map pane...well I'm not sure how to do that yet.
http://leafletjs.com/reference.html#map-panes
http://leafletjs.com/reference.html#featuregroup
According to Leaflet API, you can use bringToFront or bringToBack on any layers to brings that layer to the top or bottom of all path layers.
Etienne
For a bit more detail, Bobby Sudekum put together a fantastic demo showing manipulation of pane z-index. I use it as a starting point all the time.
Here's the key code:
var topPane = L.DomUtil.create('div', 'leaflet-top-pane', map.getPanes().mapPane);
var topLayer = L.mapbox.tileLayer('bobbysud.map-3inxc2p4').addTo(map);
topPane.appendChild(topLayer.getContainer());
topLayer.setZIndex(7);
Had to solve this recently, but stumbled upon this question.
Here is a solution that does not rely on CSS hacks and works with layer groups. It essentially removes and re-adds layers in the desired order.
I submit this as a better "best practice" than the current answer. It shows how to manage the layers and re-order them, which is also useful for other contexts. The current method uses the layer Title to identify which layer to re-order, but you can easily modify it to use an index or a reference to the actual layer object.
Improvements, comments, and edits are welcome and encouraged.
JS Fiddle: http://jsfiddle.net/ob1h4uLm/
Or scroll down and click "Run code snippet" and play with it. I set the initial zoom level to a point that should help illustrate the layerGroup overlap effect.
function LeafletHelper() {
// Create the map
var map = L.map('map').setView([39.5, -0.5], 4);
// Set up the OSM layer
var baseLayer = L.tileLayer(
'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18
}).addTo(map);
var baseLayers = {
"OSM tiles": baseLayer
};
this.map = map;
this.BaseLayers = {
"OSM tiles": baseLayer
};
this.LayersControl = L.control.layers(baseLayers).addTo(map);
this.Overlays = [];
this.AddOverlay = function (layerOptions, markers) {
var zIndex = this.Overlays.length;
var layerGroup = L.layerGroup(markers).addTo(map);
this.LayersControl.addOverlay(layerGroup, layerOptions.title);
this.Overlays.push({
zIndex: zIndex,
LeafletLayer: layerGroup,
Options: layerOptions,
InitialMarkers: markers,
Title: layerOptions.title
});
return layerGroup;
}
this.RemoveOverlays = function () {
for (var i = 0, len = this.Overlays.length; i < len; i++) {
var layer = this.Overlays[i].LeafletLayer;
this.map.removeLayer(layer);
this.LayersControl.removeLayer(layer);
}
this.Overlays = [];
}
this.SetZIndexByTitle = function (title, zIndex) {
var _this = this;
// remove overlays, order them, and re-add in order
var overlays = this.Overlays; // save reference
this.RemoveOverlays();
this.Overlays = overlays; // restore reference
// filter overlays and set zIndex (may be multiple if dup title)
overlays.forEach(function (item, idx, arr) {
if (item.Title === title) {
item.zIndex = zIndex;
}
});
// sort by zIndex ASC
overlays.sort(function (a, b) {
return a.zIndex - b.zIndex;
});
// re-add overlays to map and layers control
overlays.forEach(function (item, idx, arr) {
item.LeafletLayer.addTo(_this.map);
_this.LayersControl.addOverlay(item.LeafletLayer, item.Title);
});
}
}
window.helper = new LeafletHelper();
AddOverlays = function () {
// does not check for dups.. for simple example purposes only
helper.AddOverlay({
title: "Marker A"
}, [L.marker([36.83711, -2.464459]).bindPopup("Marker A")]);
helper.AddOverlay({
title: "Marker B"
}, [L.marker([36.83711, -3.464459]).bindPopup("Marker B")]);
helper.AddOverlay({
title: "Marker C"
}, [L.marker([36.83711, -4.464459]).bindPopup("Marker c")]);
helper.AddOverlay({
title: "Marker D"
}, [L.marker([36.83711, -5.464459]).bindPopup("Marker D")]);
}
AddOverlays();
var z = helper.Overlays.length;
ChangeZIndex = function () {
helper.SetZIndexByTitle(helper.Overlays[0].Title, z++);
}
ChangeZIndexAnim = function () {
StopAnim();
var stuff = ['A', 'B', 'C', 'D'];
var idx = 0;
var ms = 200;
window.tt = setInterval(function () {
var title = "Marker " + stuff[idx++ % stuff.length];
helper.SetZIndexByTitle(title, z++);
}, ms);
}
StopAnim = function () {
if (window.tt) clearInterval(window.tt);
}
#map {
height: 400px;
}
<link rel="stylesheet" type="text/css" href="http://cdn.leafletjs.com/leaflet-0.6.4/leaflet.css">
<script type='text/javascript' src="http://cdn.leafletjs.com/leaflet-0.6.4/leaflet.js"></script>
<div id="map"></div>
<input type='button' value='Remove overlays' onclick='helper.RemoveOverlays();' />
<input type='button' value='Add overlays' onclick='AddOverlays();' />
<input type='button' value='Move bottom marker to top' onclick='ChangeZIndex();' />
<input type='button' value='Change z Index (Animated)' onclick='ChangeZIndexAnim();' />
<input type='button' value='Stop animation' onclick='StopAnim();' />
I've found this fix (css):
.leaflet-map-pane {
z-index: 2 !important;
}
.leaflet-google-layer {
z-index: 1 !important;
}
found it here: https://gis.stackexchange.com/questions/44598/leaflet-google-map-baselayer-markers-not-visible
I cannot seem to figure out why the object returned by getProjection() is undefined. Here is my code:
// Handles the completion of the rectangle
var ne = recBounds.getNorthEast();
var sw = recBounds.getSouthWest();
$("#map_tools_selat").attr( 'value', sw.lat() );
$("#map_tools_nwlat").attr( 'value', ne.lat() );
$("#map_tools_selng").attr( 'value', ne.lng() );
$("#map_tools_nwlng").attr( 'value', sw.lng() );
// Set Zoom Level
$("#map_tools_zoomlevel").attr( 'value', HAR.map.getZoom()+1 );
document.getElementById("map_tools_centerLat").value = HAR.map.getCenter().lat();
document.getElementById("map_tools_centerLong").value = HAR.map.getCenter().lng();
// All this junk below is for getting pixel coordinates for a lat/lng =/
MyOverlay.prototype = new google.maps.OverlayView();
MyOverlay.prototype.onAdd = function() { }
MyOverlay.prototype.onRemove = function() { }
MyOverlay.prototype.draw = function() { }
function MyOverlay(map) { this.setMap(map); }
var overlay = new MyOverlay(HAR.map);
var projection = overlay.getProjection();
// END - all the junk
var p = projection.fromLatLngToContainerPixel(recBounds.getCenter());
alert(p.x+", "+p.y);
My error is: Cannot call method 'fromLatLngToContainerPixel' of undefined
Actually, i the reason why this happens is because the projection object is created after the map is idle after panning / zooming. So, a better solution is to listen on the idle event of the google.maps.Map object, and get a reference to the projection there:
// Create your map and overlay
var map;
MyOverlay.prototype = new google.maps.OverlayView();
MyOverlay.prototype.onAdd = function() { }
MyOverlay.prototype.onRemove = function() { }
MyOverlay.prototype.draw = function() { }
function MyOverlay(map) { this.setMap(map); }
var overlay = new MyOverlay(map);
var projection;
// Wait for idle map
google.maps.event.addListener(map, 'idle', function() {
// Get projection
projection = overlay.getProjection();
})
I kind of figured out what was going on. Even though it is still not crystal clear why this happens, I know that I had to instantiate the variable "overlay" right after instantiating my google map (HAR.map). So I practically moved that code snippet into my HAR class and now i use:
HAR.canvassOverlay.getProjection().fromLatLngToContainerPixel( recBounds.getCenter() );
So now, every time I create a map via my class "HAR" I also have a parallel OverlayView object within my class.
The Error could have been with losing scope of my class object, but I think it was more of the map event "projection_changed" not being fired. I got a hint from the map API docs for map class, under method getProjection():
"Returns the current Projection. If the map is not yet initialized (i.e. the mapType is still null) then the result is null. Listen to projection_changed and check its value to ensure it is not null."
If you are getting the similar issue, make sure that you assign your overlayView.setMAP( YOUR_MAP_OBJECT ) closely after instantiating the map object.