OpenLayers 6 - ES6 project structure - javascript

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.

Related

FlyTo on existing Mapbox Object in Svelte

I have a Svelte application with a Map component as below. I'd like to pass in props for centreCoords and Zoom and then fly to them when they change outside the component.
However, I can't work out how to get the map object outside the onMount function.
<script>
import { onMount, onDestroy } from 'svelte';
import mapboxgl from 'mapbox-gl';
const token = 'xxx';
let mapElement;
let map;
export let zoom;
export let centreCoords = [];
// This but doesn't work
map.flyTo({
center: centreCoords,
zoom: zoom,
essential: true // this animation is considered essential with respect to prefers-reduced-motion
});
onMount(() => {
mapboxgl.accessToken = token;
map = new mapboxgl.Map({
container: mapElement,
zoom: zoom,
center: centreCoords,
// Choose from Mapbox's core styles, or make your own style with Mapbox Studio
style: 'mapbox://styles/mapbox/light-v10',
antialias: true // create the gl context with MSAA antialiasing, so custom layers are antialiased
});
});
onDestroy(async () => {
if (map) {
map.remove();
}
});
</script>
<main>
<div bind:this={mapElement} />
</main>
<style>
#import 'mapbox-gl/dist/mapbox-gl.css';
main div {
height: 800px;
}
</style>
Should be possible to just use a reactive statement with an if:
$: if (map) map.flyTo(...)

Click on zoom button trigger go thought all div and trigger my map function Anuglar10 / OpenLayer6

I have an issue using event with OpenLayers and Angular, I have an function on click on my map which catch me the lonlat from where I have clicked on the map but, when I click on the zoom in and out control the function is also triggered same thing when I drag my view which I don't what to happen. It's the first time i'm using this API, can someone help me ?
my TS file
import { Component, OnInit } from '#angular/core';
import Map from 'ol/Map';
import Tile from 'ol/layer/Tile';
import OSM from 'ol/source/OSM';
import View from 'ol/View';
import { fromLonLat, transform } from 'ol/proj';
import VectorSource from 'ol/source/Vector';
import VectorLayer from 'ol/layer/Vector';
import { Feature } from 'ol';
import Point from 'ol/geom/Point';
import Style from 'ol/style/Style';
import Icon from 'ol/style/Icon'
#Component({
selector: 'app-indicator-acceuil',
templateUrl: './indicator-acceuil.component.html',
styleUrls: ['./indicator-acceuil.component.scss']
})
export class IndicatorAcceuilComponent implements OnInit {
map: any;
markerSource: VectorSource = new VectorSource({});
constructor() { }
ngOnInit(): void {
this.initilizeMap();
}
initilizeMap() {
this.map = new Map({
target: 'map',
layers: [
new Tile({
source: new OSM()
}),
new VectorLayer({
source: this.markerSource,
style: new Style({
image: new Icon({
anchor : [0.5,1],
src: 'assets/pin24x24.png',
scale : 1.5,
imgSize: [24,24],
})
})
})
],
view: new View({
center: fromLonLat([2.213749,46.227638]),
zoom: 6
}),
});
}
getCoord(event: any) {
const coordinate = this.map.getEventCoordinate(event);
let lonlat = transform(coordinate, 'EPSG:3857', 'EPSG:4326');
const lon = lonlat[0];
const lat = lonlat[1];
console.log(lonlat)
this.addMarker(lon, lat);
}
addMarker(lon: number, lat: number) {
this.markerSource.clear();
let marker = new Feature({
geometry: new Point(fromLonLat([lon,lat])) // dont worry about coordinate type 0,0 will be in west coast of africa
});
this.markerSource.addFeature(marker)
}
}
My html file
<div id="map" (click)="getCoord($event)"></div>

React-Leaflet offline storage of tiles

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

What is the good way to create custom GeoJSON component

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;

Openlayers map undefined in a class

I'm kinda new in an angular (and javascript generally). I have this code
import {Injectable, OnInit} from '#angular/core';
import OlMap from 'ol/map';
import OSM from 'ol/source/osm'
import OlXYZ from 'ol/source/xyz';
import OlTileLayer from 'ol/layer/tile';
import OlView from 'ol/view';
import OlProj from 'ol/proj';
#Injectable()
export class MapService {
public map: OlMap;
private _source: OlXYZ;
private _layer: OlTileLayer;
private _view: OlView;
constructor() { }
/**
* Function initializes the map
* #returns {} Object of map
*/
initMap() {
this._source = new OSM({
});
this._layer = new OlTileLayer({
source: this._source
});
this._view = new OlView({
center: OlProj.fromLonLat([6.661594, 50.433237]),
zoom: 10,
});
this.map = new OlMap({
target: 'map',
layers: [this._layer],
view: this._view
});
this.map.on("moveend", function () {
console.log(this.map);
})
}
}
The problem is on the last line. I'm trying to console log the object of map on moveend (so when user drag the map and release button- I want to load some data depends on the center of a map). But the console says the object this.map is undefined (even I'm calling a method on that object and It's working fine- It's called on mouse button release.
I guess It's gonna be something javascript special, some local or global object references, etc.
Can anyone help me, please?
NOTE: the initMap() method is called in the map component like this
ngOnInit() {
this.map = this.mapService.initMap();
console.log(this.mapService.map);
}
(In this console.log case its working fine, there is object of type _ol_map)
Your problem is the diff between function () {} and () => {}.
In function () {}, the context of "this" is the caller of the function so here is "OlMap".
In () => {}, the context of "this" is where you create it so here is "MapService".
this.map doesn't exist in OlMap.
To fix your issue, simply replace function () by () => into this.map.on().

Categories