I'm currently working in a react web-app. We are using leaflet to render some buildings maps. The app is working good in desktop. But we found the maps images are not loaded properly when using mobile navigator.
The map started loading but seems cropped, until you switch to other app and return to the app/page again you can see the full image. In some pages the image seems cropped but when scroll or zoom-in, zoom-out the image loads well.
I have tried to use invalidateSize() function but seems not working.
useEffect(() => {
let mapAttributes = {
attributionControl: false,
minZoom: -4,
zoomDelta: 0.25,
zoomSnap: 0.25,
wheelPxPerZoomLevel: 150,
crs: L.CRS[coordinateSystem],
zoomControl: isShowZoomControl,
};
const map = L.map(mapRef.current, {
...mapAttributes,
});
L.imageOverlay(backgroundImageUrl, mapBounds).addTo(map);
const defaultMarkerPaneZIndex = 600;
map.createPane(mapPanes.markerNotifications);
map.getPane(mapPanes.markerNotifications).style.zIndex = defaultMarkerPaneZIndex + 1;
setMapObj(map);
return () => {
setMapObj(null);
map.off();
map.remove();
};
}, [
backgroundImageUrl,
buildingUuid,
controlBoundariesCallback,
coordinateSystem,
floorUuid,
isShowZoomControl,
mapBounds,
]);
useEffect(() => {
if (!mapObj) return;
mapObj.invalidateSize();
}, [mapObj);
Related
Background
I am using deck.gl's PolygonLayer to render data that looks like this:
data.json:
{
"someKey": "someValue",
"spatialReference": {
"wkid": 23032,
},
"features": [
{
"attributes": {
"polygonName": "MY_POLYGON"
},
"geometry": {
"rings": [
[
[421334, 7240529], ...
],
[
[422656, 7250696], ...
]
]
}
}
]
}
Now, the problem is that decg.gl uses a latitude-longitude coordinate-system, which is different from what this polygon is expressed in.
Deck.GL documentation on rendering layers with different coordinate-systems
So, according to the documentation, deckGL renders each layer separately based on its coordinate system. Therefore, it was important to specify both coordinateOrigin and coordinateSystem props.
Understanding the coordinate system in data.json
So, as far as I understood, the spacialReference value in data.json represents an EPSG code. Using this website, I was able to find a value for the coordinateOrigin prop as [63.510617, 9.210989, 0]. As for the coordinateSystem prop, I used COORDINATE_SYSTEM.METER_OFFSETS. Here's the code:
PolygonLayer.tsx:
import React from "react";
import { COORDINATE_SYSTEM } from "#deck.gl/core/typed";
import { DeckLayer } from "#deck.gl/arcgis";
import { PolygonLayer } from "#deck.gl/layers/typed";
import MapView from "#arcgis/core/views/MapView";
import ArcGISMap from "#arcgis/core/Map";
import "#arcgis/core/assets/esri/themes/light/main.css";
export default function PolygonLayer({layerURL}) {
const mapRef = React.useState(null)
React.useEffect(() => {
fetch(layerURL)
.then(res => res.json())
.then(data => {
const blobURL = new Blob([JSON.stringify(data)], {type: "application/json",});
const url = URL.createObjectURL(blobURL); // this is needed for the layer
const layer = new PolygonLayer({
id: data["features"][0]["attributes"]["polygonName"], // correctly defined
data: url,
filled: true,
getLineWidth: 3,
getLineColor: [255, 255, 255, 0],
getFillColor: [234, 243, 221, 0],
coordinateOrigin: [63.510617, 9.210989, 0], // based on the explanation above
coordinateSystem: COORDINATE_SYSTEM.METER_OFFSETS,
getPolygon: (d) => {
console.log(d); // doesn't log anything
return d.features[0].geometry.rings;
},
});
const deckLayer = new DeckLayer({
"deck.layers": [layer],
});
const arcgisMap = new ArcGISMap({
basemap: "topo-vector",
layers: [deckLayer]
});
new MapView({
container: mapRef?.current,
map: arcgisMap,
center: data["features"][0]["geometry"]["rings"][0][0], // correctly defined
zoom: 9
});
})
.catch(err => console.log(err));
}, [layerURL]);
return <div ref={mapRef} style={{height: "90vh", width: "100%"}}></div>
}
The issue with this code
The problem with this code is that it doesn't render the layer (or the base map) and there's nothing logged in the console for the value of d; as mentioned in the code above.
Making sure the code works
Now, just a sanity check, I have used this url which returns polygons data in the standard LAT LONG format, without using coordinateOrigin or coordinateSystem props as in this example and it worked. So the code is ok rendering LAT LONG system, but breaks when using METERS_OFFSET as in the code provided.
Therefore
Have I figured out the coordinateOrigin correctly? And how can I use this (or another type of) layer to render this data correctly? Any help is appreciated and apologies for the long question!
I'm trying to integrate a leaflet map into my application.
I've written the following code in my .ts file:
ionViewDidEnter() {
this.leafletMap();
}
leafletMap() {
this.map = Leaflet.map('map').setView([49.992863, 8.247253], 5);
Leaflet.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© XY',
}).addTo(this.map);
}
In the template file I've added the div container: <div id="map" style="width: 100%; height: 200px">
When I'm running it, everything works fine. But now I would like to add some markers with data, which is loaded from an api. Therefore, I have to modify the code so, that the map (and the markers) are added after the data has loaded. So, my new code is the following:
ionViewDidEnter() {
this.dataService.getLocation(this.locationID).then(data => {
this.location = data;
this.leafletMap();
});
}
So now I'm receiving the location data, but I'm also getting an error message: Error: Uncaught (in promise): Error: Map container not found.
I've tried different things from other questions here, but nothing works. Do you have an idea, how I can solve this problem?
can you try this:
leafletMap() {
setTimeout( () => {
this.map = Leaflet.map('map').setView([49.992863, 8.247253], 5);
Leaflet.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© XY',
}).addTo(this.map);
}, 2000);
}
I had the same problem with here maps. This was working for me
In my React Native app, I'm using react-native-geolocation-service to track location changes. In iOS, the background location tracking works perfectly just by following these instructions. The problem arises in Android which causes the tracking to stop or work randomly when the app goes into background.
Let me emphasize that I don't want the location to be tracked when the app is fully closed. I ONLY want the tracking to work when the app is in the foreground (active) and background states.
I've followed the instructions given in the package's own example project to configure and start the tracking service and just like them I use react-native-foreground-service.
This is the function responsible for tracking the user location with the watchPosition method of Geolocation:
// Track location updates
export const getLocationUpdates = async (watchId, dispatch) => {
// Check if app has permissed
const hasPermission = await hasLocationPermission();
// Show no location modal and return if it hasn't
if (!hasPermission) {
dispatch(setAvailability(false));
return;
}
// Start the location foreground service if platform is Android
if (Platform.OS === 'android') {
await startForegroundService();
}
// Track and update the location refernce value without re-rendering
watchId.current = Geolocation.watchPosition(
position => {
// Hide no location modal
dispatch(setAvailability(true));
// Set coordinates
dispatch(
setCoordinates({
latitude: position?.coords.latitude,
longitude: position?.coords.longitude,
heading: position?.coords?.heading,
}),
);
},
error => {
// Show no location modal
dispatch(setAvailability(false));
},
{
accuracy: {
android: 'high',
ios: 'best',
},
distanceFilter: 100,
interval: 5000,
fastestInterval: 2000,
enableHighAccuracy: true,
forceRequestLocation: true,
showLocationDialog: true,
},
);
};
And this is how the foreground service of react-native-foreground-service is initialized:
// Start the foreground service and display a notification with the defined configuration
export const startForegroundService = async () => {
// Create a notification channel for the foreground service
// For Android 8+ the notification channel should be created before starting the foreground service
if (Platform.Version >= 26) {
await VIForegroundService.getInstance().createNotificationChannel({
id: 'locationChannel',
name: 'Location Tracking Channel',
description: 'Tracks location of user',
enableVibration: false,
});
}
// Start service
return VIForegroundService.getInstance().startService({
channelId: 'locationChannel',
id: 420,
title: 'Sample',
text: 'Tracking location updates',
icon: 'ic_launcher',
});
};
And this is how it's supposed to stop:
// Stop the foreground service
export const stopLocationUpdates = watchId => {
if (Platform.OS === 'android') {
VIForegroundService.getInstance()
.stopService()
.catch(err => {
Toast.show({
type: 'error',
text1: err,
});
});
}
// Stop watching for location updates
if (watchId.current !== null) {
Geolocation.clearWatch(watchId.current);
watchId.current = null;
}
};
The way I start the tracking is just when the Map screen mounts:
const watchId = useRef(null); // Location tracking reference value
const dispatch = useDispatch();
// Start the location foreground service and track user location upon screen mount
useMemo(() => {
getLocationUpdates(watchId, dispatch);
// Stop the service upon unmount
return () => stopLocationUpdates(watchId);
}, []);
I still haven't found a way to keep tracking the location when the app goes into background and have become frustrated with react-native-foreground-service since its service won't stop even after the app is fully closed (The problem is that the cleanup function of useMemo never gets called upon closing the app).
I have heard about react-native-background-geolocation (The free one) but don't know if it will still keep tracking after closing the app (A feature I DON'T want) and am trying my best not to use two different packages to handle the location service (react-native-background-geolocation and react-native-geolocation-service).
Another option would be Headless JS but even with that I'm not quite sure if it would stop tracking after the app is closed.
I welcome and appreciate any help that might guide me to a solution for this frustrating issue.
it's work on leaflet online map like this
<script>
import L from 'leaflet';
export default {
mounted() {
var map = L.map('map').setView([25.042474, 121.513729], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 13,
minZoom:2
}).addTo(map);
},
};
</script>
when i try to make map tiles locally then nothing show up on html
( using MobileAtlasCreator make map tiles)
<script>
import L from 'leaflet';
export default {
mounted() {
var map = L.map('map').setView([25.042474, 121.513729], 13);
L.tileLayer('./img/mapTiles/{z}/{x}/{y}.png', {
maxZoom: 13,
minZoom:2
}).addTo(map);
},
};
</script>
this is my reference below
HTML offline map with local tiles via Leaflet
https://www.youtube.com/watch?v=oP4bCLtXIeY
thanks a lot
Since you are planning to work (or use it afterwards) in a local environment, I would take out the import snippet from the code. It's mounted, I get it; but just work locally, after you get those tiles downloaded.
Put them in a folder, and load them using the code you have.
Your code is fine!
Here is my example:
var m = {x: 41.892594, y: 12.484371};
var SELF_Map = L.tileLayer('empire/{z}/{x}/{y}.jpg', {
//attribution: 'none',
continuousWorld: false,
minZoom: 4,
maxZoom: 10,
tap: false
}).addTo(map);
map.setView({lat: m.x, lng: m.y}, 6);
It worked for me every time. I get the images from my favorite map provider, I download them, and store them in a folder, in this case, a folder called "empire".
I am scaffolding a simple prototype using google places and maps api using Vue.
In index.html, I added the script tag, libraries, and api key, I get 200 response in the network tab.
<script src="https://maps.googleapis.com/maps/api/js?key=My-api-key&libraries=places" async defer></script>
In App.vue, I added the following
<input ref="autocomplete" type="text" />
...
<script>
export default {
name: "App",
data() {
return {
autocomplete: "",
};
},
methods: {},
mounted() {
const center = { lat: 40.84498856765032, lng: -73.71060855293794 };
// Create a bounding box with sides ~10km away from the center point
const defaultBounds = {
north: center.lat + 0.1,
south: center.lat - 0.1,
east: center.lng + 0.1,
west: center.lng - 0.1,
};
const originAutoComplete = google.maps.places.AutoComplete(
this.$refs["autocomplete"],
{
bounds: defaultBounds,
}
);
originAutoComplete.addListener("place_changed", () => {
console.log(originAutoComplete.getPlace());
});
How do I resolve this error? Is there a way to initialize this in App.vue script tag? The example google developers youtube works great, but I'd like to implement this in a Vue context.
One other note, I tried adding window before the .google, no dice.
UPDATE 1
as per your advice, I installed googlemaps api loader and in App.vue:
<script>
import { Loader } from "#googlemaps/js-api-loader";
const loader = new Loader({
apiKey: "YOUR_API_KEY",
version: "weekly",
libraries: ["places"],
});
export default {
name: "App",
data() {
return {
autocomplete: "",
};
},
methods: {
printData() {
console.log(this.$refs["autocomplete"].value);
},
},
async mounted() {
let origin;
const center = { lat:40.84498856765032, lng:-73.71060855293794 };
// Create a bounding box with sides ~10km away from the center point
const defaultBounds = {
north: center.lat + 0.1,
south: center.lat - 0.1,
east: center.lng + 0.1,
west: center.lng - 0.1,
};
loader
.load()
.then((google) => {
origin = new google.maps.places.Autocomplete(
this.$refs["autocomplete"],
{
types: ["cities"],
defaultBounds,
fields: ["place_id", "geometry", "name"],
}
);
origin.addListener("place_changed", () => {
console.log(origin.getPlace());
});
})
.catch((e) => {
console.log(e);
// do something
});
Now this issue is the drop down of selections is not appearing.
The issue is that your Vue App JS is loading before the google maps JS. There are some options:
Block your app loading on Google Maps by removing the async and defer from the script. Make sure the script tag is above your Vue JS after removing the async/defer.
Incorporate a callback parameter or just check for existence of window.google in your vue component and allow vue to update when it is available.
Use #googlemaps/js-api-loader in the Vue component. See Is `async/await` available in Vue.js `mounted`? for ways to incorporate.
I recommend option 3.