I've seen a common solution to storing tiles offline with Leaflet by using localforage etc like this:
const map = L.map("map-id");
const offlineLayer = L.tileLayer.offline('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', localforage, {
minZoom: 13,
maxZoom: 19,
crossOrigin: true
});
offlineLayer.addTo(map);
I'm attempting to do this with React-Leaflet. I've read that you would have to extend the
TileLayer class. However, extending does not seem to work with React-Leaflet v2. enable react-leaflet to use be usable offline
Has anyone come up with a solution to this. Other than not using react-leaflet and using leaflet directly?
I'm working on the same thing. Didn't look at v2 but in v3 I think something like this is what is needed:
import { LayerProps, createTileLayerComponent, updateGridLayer, withPane } from '#react-leaflet/core';
import { TileLayer as LeafletTileLayer, TileLayerOptions } from 'leaflet';
import TileLayerOffline from 'leaflet.offline';
import 'leaflet';
import L from 'leaflet'
import '#react-leaflet/core';
export interface TileLayerProps extends TileLayerOptions, LayerProps {
url: string;
}
export const TileLayerOfflineExtended = createTileLayerComponent<LeafletTileLayer, TileLayerProps>(
function createTileLayer({ url, ...options }, context) {
return {
instance: (L.tileLayer as any).offline(url, withPane(options, context)),
context
};
},
updateGridLayer
);
What I haven't figured out is getting the controls to work. Would be useful if someone posted it here and we can get it into a react-leaflet plugin npm
Related
I'm working on a project using OpenLayers 6 in ES6 with Webpack.
It's my first real ES6 project and I want to make it organized (and a bit modular) but I'm struggling with the use of imports and exports.
Currently my structure is :
- all.js
- map/
- index.js
- gpx.js
The all.js file is the "entry point".
all.js
import 'ol/ol.css';
import map from './map/index';
import { vector as GPXvector } from './map/gpx';
map.addLayer(GPXvector);
map/index.js
import { Map, View } from 'ol';
import { OSM } from 'ol/source';
import { Tile as TileLayer } from 'ol/layer';
const map = new Map({
layers: [
new TileLayer({
source: new OSM()
})
],
target: 'map',
view: new View({
center: [1037749, 5135381],
zoom: 10
})
});
export { map as default };
map/gpx.js
import { Vector as VectorLayer } from 'ol/layer';
import { Circle as CircleStyle, Fill, Stroke, Style } from 'ol/style';
import { unByKey } from 'ol/Observable';
import VectorSource from 'ol/source/Vector';
import GPX from 'ol/format/GPX';
import map from './index.js'; // Is that good ??
const style = {
// [...] Some style here
};
const source = new VectorSource({
url: 'test.gpx',
format: new GPX()
});
var onChange = source.on('change', function() {
if (source.getState() == 'ready') {
map.getView().fit(source.getExtent()); // Access to "map" from "index.js" HERE
unByKey(onChange);
}
});
const vector = new VectorLayer({
source: source,
style: function (feature) {
return style[feature.getGeometry().getType()];
}
});
export { vector, source };
I want to access to the map instance (initialized in map/index.js) from the map/gpx.js file (see comment in source code).
But I feel like I am importing map from map/index.js inside all.js, which is importing map/gpx.js which himself also imports map from map/index.js.
It sounds to me like some kind of "loop" imports where it will be a mess to handle the order of imports for example when I'll get more files in my project.
Also if you have any advice for me to start properly with ES6 it's cool !
EDIT 1
I changed to something else to see if it allows more granularity.
all.js
import 'ol/ol.css';
import map from './ntrak/index';
import MyGPX from './ntrak/gpx';
const gpx = new MyGPX(map, 'test.gpx');
map/gpx.js
import { Vector as VectorLayer } from 'ol/layer';
import { Circle as CircleStyle, Fill, Stroke, Style } from 'ol/style';
import { unByKey } from 'ol/Observable';
import VectorSource from 'ol/source/Vector';
import GPX from 'ol/format/GPX';
const style = {
// [...] Some style here
};
const _onSourceChange = function(map, source) {
if (source.getState() == 'ready') {
map.getView().fit(source.getExtent());
unByKey(_onSourceChange);
}
}
export default class {
constructor(map, url, fit = true) {
this.map = map;
this.url = url;
this.fit = fit;
this.loadGPX();
}
loadGPX() {
this.source = new VectorSource({
url: this.url,
format: new GPX()
});
if (this.fit) {
this.source.on('change', () => _onSourceChange(this.map, this.source));
}
this.vector = new VectorLayer({
source: this.source,
style: function(feature) {
return style[feature.getGeometry().getType()];
}
});
this.map.addLayer(this.vector);
}
};
I think it's cool because it allows to get multiple GPX vectors on the same map instance.
But if I want to do more stuff that interacts with my GPX source or vector I will need to pass the instance everytime instead of just importing the GPX file directly.
What do you think?
You can use CircularDependencyPlugin for webpack to track such circular dependencies.
There is no circular dependency in your example, import map from './index.js'; // Is that good ?? is ok.
Your es6 code is fine to me, I see one var usage (var onChange = ...), you should replace that.
I'm trying to update a map to my current location using a vue onClick which updates props and sends them to my map component. I am using a :key to rerender my map component when my map data changes and I get some new x,y for my map center. (based on the esri/arcgis example I would need to rebuild the map, if anyone knows this to be wrong let me know please)
VUE js arcgis starting documentation:
https://developers.arcgis.com/javascript/latest/guide/vue/
for some reason my map does render again and seems like it's about to load but then it just stays blank.
maybe someone can tell me if this is an issue with the component still persisting in some way after I force it to render again?
my app.vue
<template>
<div id="app">
<web-map v-bind:centerX="lat" v-bind:centerY="long" ref="mapRef"/>
<div class="center">
<b-button class="btn-block" #click="getLocation" variant="primary">My Location</b-button>
</div>
</div>
</template>
<script>
import WebMap from './components/webmap.vue';
export default {
name: 'App',
components: { WebMap },
data(){
return{
lat: -118,
long: 34,
}
},
methods:{
showPos(pos){
this.lat = pos.coords.latitude
this.long = pos.coords.longitude
this.$refs.mapRef.updateCoordinates()
console.log('new location',this.lat,this.long, this.$refs)
},
getLocation(){
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(this.showPos);
} else {
console.log("Geolocation is not supported by this browser.");
}
},
},
};
</script>
my map component
<template>
<div></div>
</template>
<script>
import { loadModules } from 'esri-loader';
export default {
name: 'web-map',
props:['centerX', 'centerY'],
data: function(){
return{
X: this.centerX,
Y: this.centerY,
view: null
}
},
mounted() {
console.log('new data',this.X,this.Y)
// lazy load the required ArcGIS API for JavaScript modules and CSS
loadModules(['esri/Map', 'esri/views/MapView'], { css: true })
.then(([ArcGISMap, MapView]) => {
const map = new ArcGISMap({
basemap: 'topo-vector'
});
this.view = new MapView({
container: this.$el,
map: map,
center: [this.X,this.Y], ///USE PROPS HERE FOR NEW CENTER
zoom: 8
});
});
},
beforeDestroy() {
if (this.view) {
// destroy the map view
this.view.container = null;
}
},
methods:{
updateCoordinates(){
this.view.centerAt([this.X,this.Y])
}
}
};
</script>
I don't think the key you're passing as a prop to web-map serves any purpose since it's not being used inside the component.
You could try, instead, to force update the component as such:
<web-map v-bind:centerX="lat" v-bind:centerY="long" ref="mapRef" />
this.refs.mapRef.$forceUpdate()
This ensures that you're force updating the whole component, but maybe there's a better solution. Instead of re-rendering the entire component, which means having to create the map once again, you could instead keep the component alive and just use an event to update the coordinates.
Based on https://developers.arcgis.com/javascript/3/jsapi/map-amd.html#centerat, you can re-center the map using the centerAt method.
That way the map component has a method like:
updateCoordinates(coord){
this.view.centerAt(coord)
}
And you can call it on the parent with
this.refs.mapRef.updateCoordinates(newCenter)
Hope it helps, let me know if you do any progress.
I think you can test Watch with setInterVal() for a loop to check your location each 1sec
I Need help to create GeoJSON custom component from React-Leaflet
Write with React and React-Leaflet (last version both)
The code works when write in the Map component, but I want to import/export it to split code
import React from 'react';
import { withLeaflet, GeoJSON } from 'react-leaflet'
import L from 'leaflet'
class CustomGesJSON extends GeoJSON {
getStyle(feature) {
// some code
}
pointToLayer(feature, latlng) {
// some code
}
onEachFeature(feature, layer) {
// some code
}
createLeafletElement(opts) {
const CustomGesJSON = L.geoJSON.extend({
onAdd: (map) => {
this.getStyle = this.getStyle.bind(this);
this.pointToLayer = this.pointToLayer.bind(this);
this.onEachFeature = this.onEachFeature.bind(this);
return this ;
}
});
return new CustomGesJSON({ data: this.props.data });
}
}
function testlog(txt) {
// some code
}
export default withLeaflet(CustomGesJSON);
I've got a error message "GeoJSON is not a constructor"
Function and method (not show here) works, I just need help to make a proper inheritance
Other solution are welcome to
Thanks for your help
It is probable the "GeoJSON" object exported by "react-leaflet" is not an ES6 class, but the Leaflet L.GeoJSON "class".
You can use Leaflet own pre-ES6 class inheritance scheme, as described in the Leaflet class theory tutorial:
const MyCustomClass = GeoJSON.extend({
options: {
onEachFeature: myCustomDefaultFunction
// etc.
}
});
export default MyCustomClass;
How can I get access to the component props before the component will be rendered?
I would like to load the google maps api with the value of the passed prop.
import * as VueGoogleMaps from 'vue2-google-maps';
import GmapCluster from 'vue2-google-maps/dist/components/cluster';
import Vue from 'vue';
Vue.use(VueGoogleMaps, {
load: {
// Get here the prop apiKey
key: '..........',
libraries: 'places', in
}
});
Vue.component('GmapCluster', GmapCluster);
export default {
name: 'api',
props: ['apiKey'],
methods: {
},
created() {
}
}
Hmn I've never heard about it and the project page is asking for contributers and this lib has more than 100 issues. So my suggestion is change the lib. I've made good experiences with https://github.com/KoRiGaN/Vue2Leaflet
You can do it on beforeMount() lifecycle hook.
beforeMount() {
loadGmapApi({
key: apiKeyProp
});
}
I have a React application that uses vega-lite for data visualizations. I tried to use the official tooltip addon for vega (https://github.com/vega/vega-tooltip), however I have no idea how to do it in a React component.
This is the component I have:
import React from 'react';
import PropTypes from 'prop-types';
import VegaLite from 'react-vega-lite';
const VegaChart = ({data, spec}) => {
return(
<div className="vega-chart-wrapper">
<VegaLite spec={spec} data={data} />
</div>
)
}
VegaChart.propTypes = {
data: PropTypes.object.isRequired,
spec: PropTypes.object.isRequired
}
export default VegaChart;
From the documentation of vega-tooltip, it says that I can install the module and then I have to do this:
<!-- Placeholder for my scatter plot -->
<div id="vis-scatter"></div>
var opt = {
mode: "vega-lite",
};
vega.embed("#vis-scatter", vlSpec, opt, function(error, result) {
// result.view is the Vega View, vlSpec is the original Vega-Lite specification
vegaTooltip.vegaLite(result.view, vlSpec);
});
This looks like the standard way to integrate in a jQuery application, but this is not gonna work in a React component. Any ideas?
Your react-vega-lite must already include vega-lite. So
import * as vega from 'vega-lite';
And put the rest in componentDidMount of your VegaChart