Angular + Leaflet + Leaflet Extra Markers - javascript

I'm making SPA website based on net.core 3 and Angular 8 with Leaflet. I wanted to use Leaflet Extra Markers but can't get this thing to work.
I used NPM to install Extra Markers:
https://www.npmjs.com/package/leaflet-extra-markers
npm i leaflet-extra-markers
So far so good. I create component - map.component.ts and below is the code. As long as I use Leaflet only everything is working fine. But then I try to add marker using Extra Markers and getting
TypeError: Cannot read property 'icon' of undefined.
I guess I'm missing something super simple but can't figure it out. Looks like Extramarkers does not extend Leaflet class? I don't know why...
Any help will be much appreciated.
import { AfterViewInit, Component } from '#angular/core';
import * as L from 'leaflet';
import { IMapObject } from '../models/map.model';
#Component({
selector: 'app-map',
templateUrl: './map.component.html',
styleUrls: ['./map.component.css']
})
export class MapComponent implements AfterViewInit {
private map;
private mapObjects: IMapObject[] = [];
constructor() { }
ngAfterViewInit(): void {
this.initMap();
}
private initMap(): void {
this.map = L.map('map',
{
center: [52.246215, 21.223158],
zoom: 18
});
const tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
{
maxZoom: 19,
attribution: '© OpenStreetMap'
});
tiles.addTo(this.map);
this.createDummyMapObjects();
this.mapObjects.map(o => {
L.marker(o.position.latlon, { title: o.name, icon: greenIcon }).addTo(this.map).bindPopup(o.description);
});
// This is not working. Getting ERROR TypeError: Cannot read property 'icon' of undefined
const redMarker = L.ExtraMarkers.icon({
icon: 'fa-coffee',
markerColor: 'red',
shape: 'square',
prefix: 'fa'
});
L.marker([51.941196, 4.512291], { icon: redMarker }).addTo(this.map);
}
private createDummyMapObjects(): void {
const obj1: IMapObject = {
id: 1,
name: 'Test',
category: 2,
description: 'Test',
position: {
latlon: new Array(52.241103, 21.190475)
}
}
const obj2: IMapObject = {
id: 1,
name: 'Test',
category: 2,
description: 'Test',
position: {
latlon: new Array(52.243149, 21.190883)
}
}
this.mapObjects.push(obj1, obj2);
}
}
EDIT: I followed advice and added script and json path to angular.json file. Now I'm getting:
Uncaught ReferenceError: L is not defined
at leaflet.extra-markers.js:16
at leaflet.extra-markers.js:13
at leaflet.extra-markers.js:14
Looks like issue with import?

According to the documentation you need to include bootstrap version 3.3.7 and font-awesome version 5.12.0 respectively in your index.html
<link
rel="stylesheet"
href="//netdna.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
/>
<!-- Font Awesome 5 SVG -->
<script
defer
src="https://use.fontawesome.com/releases/v5.12.0/js/all.js"
></script>
Make sure you include these in your ts or the last 2 in your angular.json:
import "leaflet/dist/leaflet.css";
import * as L from "leaflet";
import "leaflet-extra-markers/dist/css/leaflet.extra-markers.min.css";
import "leaflet-extra-markers/dist/js/leaflet.extra-markers.js";
Demo

Did you include stylesheet and script in angular json?
"scripts": [
"node_modules\leaflet-extra-markers\dist\js\leaflet.extra-markers.js"
],
styles": [
"styles.css",
"node_modules\leaflet-extra-markers\dist\css\leaflet.extra-markers.min.css"
],

Related

How to use deck.gl to render a polygon layer that is using a different coordinate system than the standard LAT LONG?

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!

Error in Angular: earthquake_GeoJSONP.js:1 Uncaught ReferenceError: eqfeed_callback is not defined at earthquake_GeoJSONP.js:1:1

I'm trying to replicate the Importing Data into Maps (Importing Data into Maps) example to my angular project but I get the following error in console:
earthquake_GeoJSONP.js:1 Uncaught ReferenceError: eqfeed_callback is not defined
at earthquake_GeoJSONP.js:1:1
app.component.ts
import { Component, OnInit } from '#angular/core';
import { Loader } from "#googlemaps/js-api-loader"
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{
title = 'map';
ngOnInit() {
let map: google.maps.Map;
const loader = new Loader({
apiKey: "YOUR_API_KEY"
});
loader.load().then(() => {
const map = new google.maps.Map(document.getElementById("map") as HTMLElement, {
zoom: 2,
center: new google.maps.LatLng(2.8, -187.3),
mapTypeId: "terrain",
});
// map.data.loadGeoJson('data.json');
// Create a <script> tag and set the USGS URL as the source.
const script = document.createElement("script");
// This example uses a local copy of the GeoJSON stored at
// http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_week.geojsonp
script.src =
"https://developers.google.com/maps/documentation/javascript/examples/json/earthquake_GeoJSONP.js";
document.getElementsByTagName("head")[0].appendChild(script);
const eqfeed_callback = function (results: any) {
for (let i = 0; i < results.features.length; i++) {
const coords = results.features[i].geometry.coordinates;
const latLng = new google.maps.LatLng(coords[1], coords[0]);
let marker = new google.maps.Marker({
position: latLng,
map: map,
});
// place marker in map
marker.setMap(map)
}
};
});
}
}
app.component.html
<div id="map"></div>
app.component.css
#map {
height: 500px;
width: 100%;
}
Installed Packages
This is the googlemaps package that I installed in my project.
npm install #googlemaps/js-api-loader
npm i -D #types/google.maps
My Angular version
Angular CLI: 13.3.7
Node: 16.14.2
Package Manager: npm 8.5.0
OS: win32 x64
What my code shows
Just appear the map but without markers.
What I hope will appear
According to the Google Maps documentation at the following link: Importing Data into Maps
Thanks for your time and support!
I confirmed that your map appears but the markers don't.
Just like you said, what you want to happen is for the markers to show.
What I did is I tried to replicate your code in codesandbox and found out two main errors:
'map' is defined but never used. (#typescript-eslint/no-unused-vars)
'eqfeed_callback' is assigned a value but never used. (#typescript-eslint/no-unused-vars)
Solutions:
Error #1: app.component.ts had an unused variable of map and it looks like this in your code:
ngOnInit() {
let map: google.maps.Map;
const loader = new Loader({
apiKey: "YOUR_API_KEY"
});
loader.load().then(() => {
const map = new google.maps.Map(document.getElementById("map") as HTMLElement, {
zoom: 2,
center: new google.maps.LatLng(2.8, -187.3),
mapTypeId: "terrain",
});
the map variable inside the loader.load().then is the one you used and the one on top is the unused which caused error #1. I just removed it and it removed the error on the console.
Error #2: your code had a variable eqfeed_callback that stored a function but was not called. I compared your code to the sample you gave here and confirmed that there was a callback on the code there.
As I am not very familiar to typescript and angular I tried experimenting on the code and made it work by making the end part of your code look like this:
const eqfeed_callback = function (results: any) {
for (let i = 0; i < results.features.length; i++) {
const coords = results.features[i].geometry.coordinates;
const latLng = new google.maps.LatLng(coords[1], coords[0]);
let marker = new google.maps.Marker({
position: latLng,
map: map
});
// place marker in map
marker.setMap(map);
}
};
window.eqfeed_callback = eqfeed_callback;
});
}
}
I just inserted window.eqfeed_callback = eqfeed_callback and it made the error disappear and at the same time show the markers on the map.
Here's the sandbox link: https://codesandbox.io/s/holy-sound-p8qtrc
Just use your own API key.
I hope this helps.

WMTS and WMTSTileGrid causing "Cannot read property 'every' of undefined"

What i'm basically doing is trying to get a Map in Open Layers, which have a view limited to Denmark. I want to do it using EPSG:25832, because i need some overlays from a specific service, which is using this projection.
I'm trying to create a WMTSTileGrid, and parse it to a TileLayer, through a WMTS, in which i'm calling a service to get my layer. I'm using ol#5.3.2.
I'm getting the following error, and i need help figuring out what is causing it:
Uncaught TypeError: Cannot read property 'every' of undefined
at isSorted (array.js:242)
at WMTSTileGrid.TileGrid (TileGrid.js:70)
at new WMTSTileGrid (WMTS.js:58)
at Object.parcelRequire.index.js.ol/ol.css (index.js:83)
at newRequire (mao.e31bb0bc.js:47)
at mao.e31bb0bc.js:81
at mao.e31bb0bc.js:120
Heres is the code, i have tried posting the minimum possible amount for easier readability, let me know if you think something is missing:
import { Map, View } from 'ol';
import TileLayer from 'ol/layer/Tile';
import WMTS from 'ol/tilegrid/WMTS';
import WMTSTileGrid from 'ol/tilegrid/WMTS';
import Group from 'ol/layer/Group.js';
import TileWMS from 'ol/source/TileWMS';
import proj4 from 'proj4/dist/proj4';
import { get as getProjection } from 'ol/proj';
import Projection from 'ol/proj/Projection.js';
import { getTopLeft } from 'ol/extent.js';
import { register } from 'ol/proj/proj4.js'; // ADDED THIS
var myServiceToken = '12345678';
// defining custom projection, because i want to use EPSG:25832 due to the service i'm calling
var projCode = 'EPSG:25832';
proj4.defs(projCode, "+proj=utm +zone=32 +ellps=GRS80 +units=m +no_defs");
register(proj4); // ADDED THIS
var myProjection = new Projection({
code: 'EPSG:25832',
units: 'm',
extent: [120000, 5661139.2, 1378291.2, 6500000]
});
var projection = getProjection(myProjection);
var projectionExtent = projection.getExtent();
var myTileGrid = new WMTSTileGrid({
origin: getTopLeft(projectionExtent),
extent: [120000, 5661139.2, 1378291.2, 6500000],
resolutions: [1638.4, 819.2, 409.6, 204.8, 102.4, 51.2, 25.6, 12.8, 6.4, 3.2, 1.6, 0.8, 0.4, 0.2],
matrixIds: ['L00', 'L01', 'L02', 'L03', 'L04', 'L05', 'L06', 'L07', 'L08', 'L09', 'L10', 'L11', 'L12', 'L13'],
});
const map = new Map({
target: 'map',
layers: [
new Group({
'title': 'Base maps',
layers: [
new TileLayer({
opacity: 1.0,
title: 'Base',
type: 'base',
visible: true, // by default this layer is visible
source: new WMTS({
url: "https://services.someService.com/some_map?token=" + myServiceToken,
layer: "some_map",
matrixSet: "View1",
format: "image/jpeg",
projection: getProjection('EPSG:25832'), // ADDED THIS
tileGrid: myTileGrid,
style: 'default',
size: [256, 256]
})
})
]
})
],
view: view
});```
You made a mistake in your imports:
import WMTS from 'ol/tilegrid/WMTS';
Should be:
import WMTS from 'ol/source/WMTS';

My button onClick recenter for OpenLayers doesn't work

I'm building an angular application using openlayers that when I click a button it will recenter my map .I'm trying to re-center my map when I onClick to a button but it doesnt work.
ERROR TypeError: Cannot read property 'setCenter' of undefined.
Can someone tell me what I'm doing wrong. Thanks in advance !
import { Component } from '#angular/core';
import {fromLonLat} from 'ol/proj'
import {view} from 'ol/View';
import * as ol from 'openlayers';
export class AppComponent {
distance = 60;
points: Array<{ x: number; y: number; }> = [];
position : Array<{ x: number; y: number; id: string; radius: number,color:string, place:string}> =
[
{x:11.5820,y:48.1351,id:"munich",radius:20, color:"red", place:"m"},
{x:13.388866,y:52.517071,id:"berlin", radius:40,color:"blue", place:"b"},
];
coords = {
berlin: [13.388866, 52.517071]
};
onClick (city: string) {
view.setCenter({
center: fromLonLat(this.coords[city]),
duration: 2000
});
}
mapOnClick(evt) {
console.log(evt);
const map = evt.map;
// this bit checks if user clicked on a feature
const p = map.forEachFeatureAtPixel(evt.pixel,
function(feature, layer) {
console.log("got feature" + feature.getId());
return feature;
});
}
}
<button id='berlin' (click)="onClick('berlin')">Zoom to Berlin</button>
If you are trying to recenter there must already be a view, but if it was constructed inside the map constructor there won't be a view variable and you will need to reference it using map.getView(). Also setCenter() doesn't do animated recentering. Assuming your map variable is map try:
map.getView().animate({
center: fromLonLat(this.coords[city]),
duration: 2000
})
Lets try this once just for suggestion,
import OlView from 'ol/View';
view: OlView;
ngOnInit() {
this.view = new OlView({
center: fromLonLat(this.coords[city]),
zoom: 3
});
}
I hope its solve your problem if you received proper data for this.coords[city] variable. You need to pass data like this, center: fromLonLat([6.661594, 10.32371]).
For more Reference,
Use OpenLayers 4 with Angular 5
You may get some idea from this above url example.
Thanks,
Muthukumar

Integrating Treant-js in Angular2 Project

I'm trying to make use of treant-js in an Angular2 project, and I'm struggling with how to properly integrate it.
I have a working vanilla JavaScript / HTML example that I'm trying to make work in Angular2.
I've created a component, added treant-js and raphael from npm, and imported them into the component.
import * as Raphael from 'raphael';
import * as Treant from 'treant-js';
I've setup the html template and the corresponding css to match the standalone javascript project.
<div class="tree-wrapper" >
<div class="chart" id="test-tree"></div>
</div>
And in the class I'm defining the tree-structure variable
private tree_structure: any = {
chart: {
container: "#test-tree",
levelSeparation: 20,
siblingSeparation: 15,
subTeeSeparation: 15,
rootOrientation: "WEST",
node: {
HTMLclass: "tree-wrapper",
drawLineThrough: true
},
connectors: {
type: "straight",
style: {
"stroke-width": 2,
"stroke": "#ccc"
}
}
},
nodeStructure: {
text: {
name: { val: "Djokovic, Novak", href: "http://... }
},
HTMLclass: "animal",
image: "flags/can.jpg",
children: [...
*** the rest of the object graph continues ***
}
In the JavaScript / HTML version, the tree is loaded in an inline script like this:
<script>
new Treant( tree_structure );
</script>
What's the proper way to initialize this in Angular2?
I was thinking something like this:
private tree: Treant = new Treant();
ngOnInit() {
tree ( this.tree_structure );
}
However Treant isn't a function.
I'm sure I'm missing something obvious here, but I can't wrap my head around this.
The solution is as follows:
Import the scripts into the index.html file - don't add them via npm, just load the js files into wherever you store your static import assets, in this case I have an assets directory at the root of the project that contains static CSS files (BootStrap) and static images, as well as a js subdirectory that contains the Treant and Raphael files.
<script src="/assets/js/raphael.js"></script>
<script src="/assets/js/Treant.js"></script>
Then create a component, and turn off ViewEncapsulation so that the Treant CSS styles will work.
import { Component, Input } from '#angular/core';
import { ViewEncapsulation } from '#angular/core'
declare var Treant: any;
#Component({
encapsulation: ViewEncapsulation.None,
selector: 'treant-tree',
styleUrls: ['treant-tree.component.scss'],
template: `
<div class="treant-class" >
<div class="chart" id="treant-id"></div>
</div>
`
})
export class TreantTree {...}
I used the Tennis Example, so create the nodeStructure as a variable in your class:
private tree_structure: any = {
chart: {
container: "#treant-id",
levelSeparation: 20,
siblingSeparation: 15,
subTeeSeparation: 15,
rootOrientation: "WEST",
node: {
HTMLclass: "treant-class",
drawLineThrough: true
},
connectors: {
type: "straight",
style: {
"stroke-width": 2,
"stroke": "#ccc"
}
}
},
nodeStructure: {
text: {
name: { val: "Djokovic, Novak", href: "http://www.atpworldtour.com/Tennis/Players/Top-Players/Novak-Djokovic.aspx" }
},
HTMLclass: "animal",
image: "/assets/images/flags/can.jpg",
children: []
}
(Watch your parentheses here - I've truncated the code above)
I've copied the flags directory with all the images into /assets/images/flags/ and changed the paths in the nodeStructure to reflect that.
I copied all the relevant CSS styles into the component's treant-tree.component.scss file.
If you log out Treant in your constructor, you should see and be able to expand/view the function object just to confirm that the script is loading properly, but this isn't necessary
`console.log`("Treant",Treant);
Then in ngOnInit, call Treant and pass in the tree-structure variable in an Immediately Invoked Function Expression. The syntax for this in TypeScript is as follows:
ngOnInit() {
(() => {Treant(this.tree_structure)})();
}
Obviously in order to make actual use of this, you'll want to abstract out the tree-structure data, probably construct it via a service and load it into the component as a parameter or #Input binding, but at least this gets everything working.

Categories