How to show self-hosted tiles using deck.gl MVtLayer? - javascript

This may be in the series of dumb questions, but when I look at https://deck.gl/docs/api-reference/geo-layers/mvt-layer, I do not understand how to make a MVTLayer that fetches self-hosted tiles without React pieces. Can someone help? This would feel to be even large interest now that buildless is also becoming a thing in web programming.
What I would like to achieve is a simple HTML (e.g. index.html) file that uses a script tag like <script src="https://unpkg.com/deck.gl#8.4.5/dist.min.js"></script> and the example from the aforementioned Deck.gl that looks like (I changed the URL)
import DeckGL from '#deck.gl/react';
import {MVTLayer} from '#deck.gl/geo-layers';
function App({viewState}) {
const layer = new MVTLayer({
data: `https://<selfhostedurl>/{z}/{x}/{y}.pbf`,
minZoom: 0,
maxZoom: 23,
getLineColor: [192, 192, 192],
getFillColor: [140, 170, 180],
getLineWidth: f => {
switch (f.properties.class) {
case 'street':
return 6;
case 'motorway':
return 10;
default:
return 1;
}
},
lineWidthMinPixels: 1
});
return <DeckGL viewState={viewState} layers={[layer]} />;
}
but instead make this a without React. I see it requires a bit more code on how to define a canvas HTML element and use it. Maplibre example would be OK too. :) There is one Maplibre example at https://codepen.io/snickell/pen/dypOWzj.

You can use the Scripting API for more 'simple' examples, here you have an example of using MVTLayer.
Deck.gl offers a standalone bundled version of the library - a native JavaScript scripting interface like that of d3.js.
As simple as
const deckgl = new deck.DeckGL({
container: 'map',
mapStyle: 'https://maps-api-v2.us.carto.com/user/public/carto/sql/{z}/{x}/{y}?source=SELECT * FROM ne_10m_railroads_public&api_key=default_public&format=mvt',
initialViewState: {
latitude: 41.4,
longitude: 2.18,
zoom: 5,
},
controller: true,
layers: [
new deck.MVTLayer({
data: 'https://d25uarhxywzl1j.cloudfront.net/v0.1/{z}/{x}/{y}.mvt',
getLineColor: [192, 192, 192],
lineWidthMinPixels: 1
})
]
});

Related

Successfully get the data from Flask but cannot render the website

I want to use the eCharts to draw the candletick picture. I get the data from flask using Axios:
request.post('/investor').then(res=>{
var arr=Object.keys(res.close)
for(let i=0;i<arr.length;i++){
this.stockdata[i]=[]
for(let j=0;j<4;j++){
var temp=[]
temp[0]=res.open[i]
temp[1]=res.close[i]
temp[2]=res.low[i]
temp[3]=res.high[i]
this.stockdata[i][j]=temp[j]
}
}
})
this my data:
The 2d array
but it cannot render the website
then i create the data using the method below(assign data manually):
this.stockdata= [
[20, 34, 10, 38],
[40, 35, 30, 50],
[31, 38, 33, 44],
[38, 15, 5, 42]
]
it successfully draw the picture,i don't know why.
and this my main function:
mounted(){
request.post('/investor').then(res=>{
var arr=Object.keys(res.close)
for(let i=0;i<arr.length;i++){
this.stockdata[i]=[]
for(let j=0;j<4;j++){
var temp=[]
temp[0]=res.open[i]
temp[1]=res.close[i]
temp[2]=res.low[i]
temp[3]=res.high[i]
this.stockdata[i][j]=temp[j]
}
}
})
console.log(this.stockdata)
var echarts = require('echarts');
var option = {
xAxis: {
data: ['2017-10-24', '2017-10-25', '2017-10-26', '2017-10-27']
},
yAxis: {},
tooltip: {
trigger: 'axis'
},
series: [
{
type: 'candlestick',
data:this.stockdata
}
]
};
var charts = echarts.init(this.$refs.myChart);
charts.setOption(option);
}
}
There is not enough information, but I can still guess.
I am going to assume that your Object.keys double for loop with i and j works fine. To be sure of that, please add console.log(this.stockdata) after both loops end running, to make sure you are building your data from the response the right way.
The error is probably because in your code you call the async function, which is calling your API.
// this *.then* happens after the initial render, and after a delay it gets a value,
// which is too late
request.post('/investor').then(res=>{
This causes that at the exact frame the App is being rendered, your response is not yet there, causing your App to break because this.stockdata is still undefined. One way to fix it is the following:
// in render function:
{this.stockdata && <YourComponent dataToRender={this.stockdata} />}
This will prevent rendering YourComponent until the API response is there

Unable to create Deck.gl layers and overlay

I installed deck.gl through npm and I'm unable to create layers. At first I was trying to create a google overlay, later I tried it with mapbox overelay. It keeps throwing same error I'm attaching error image and code snippet. I'm using it with angular and I'm trying to create an overlay in my ngOnInit of component.
Thanks!
[![error image][1]][1]
import {ScatterplotLayer} from '#deck.gl/layers';
import {MapboxLayer} from '#deck.gl/mapbox';
//below in my ngOnInit
ngOnInit() {
this.initializeManagers();
this.addListenerForAddressInput();
const myDeckLayer = new MapboxLayer({
id: 'my-scatterplot',
type: ScatterplotLayer,
data: [
{position: [-74.5, 40], size: 100}
],
getPosition: d => d.position,
getRadius: d => d.size,
getColor: [255, 0, 0]
});
}
[1]: https://i.stack.imgur.com/ASRuw.png
Ok so the issue was with npm modules of deck.gl it was using loaders.gl so I installed it but it didn't work so I manually imported script in my index.html and it worked.
<script src="https://unpkg.com/deck.gl#8.7.3/dist.min.js"></script>
<script src="https://unpkg.com/#deck.gl/google-maps#8.7.3/dist.min.js"></script>

How to access ArcGIS JavaScript API outside of require callback?

I am trying to figure out how I can access the ArCGIS JS API from a map after the map has been rendered, outside of require (ArcGIS JS API uses Dojo). For example, so I can do stuff like add (or remove) points, and perform other operations on the map.
I can create a map as follows:
require(["esri/config", "esri/Map", "esri/views/MapView", "esri/Graphic",
"esri/layers/GraphicsLayer"
], function(esriConfig, Map, MapView, Graphic, GraphicsLayer) {
esriConfig.apiKey = "";
const map = new Map({
basemap: "arcgis-topographic"
});
const view = new MapView({
map: map,
center: [-81, 41],
zoom: 9,
container: "viewDiv"
});
});
And I can add a point using this function:
function plotPoint(lat, long, props) {
const popupTemplate = {
title: "{Name}",
content: "{Description}"
}
const attributes = {
Name: props.name,
Description: props.desc
}
const graphicsLayer = new GraphicsLayer();
map.add(graphicsLayer);
const point = {
type: "point",
longitude: long,
latitude: lat
};
const simpleMarkerSymbol = {
type: "simple-marker",
color: [226, 119, 40],
outline: {
color: [255, 255, 255],
width: 1
}
};
const pointGraphic = new Graphic({
geometry: point,
symbol: simpleMarkerSymbol,
attributes: attributes,
popupTemplate: popupTemplate
});
graphicsLayer.add(pointGraphic);
}
But plotPoint needs to be within the require callback so it can access the referenced modules (like GraphicsLayer). I could assign it to the global window object so I could call it outside of require, but then I may run into an issue where the function is called before it's defined.
I may need to perform other operations too from other points in the code, like removing points, adding feature layers, etc. Unfortunately, this must all exist inside some legacy code, so I can't refactor the entire application.
Is there a better pattern for accessing the API outside of require?
I think that the easy way to achieve what you want, if I understand you correctly, is just to define modules and include it in you application.
A simple example base on you code would be something like this,
GraphicsManager.js
define([
"dojo/_base/declare",
"esri/Graphic",
"esri/layers/GraphicsLayer"
], function(declare, Graphic, GraphicsLayer){
return declare(null, {
plotPoint: function(lat, long, props){
// .. here the logic
return graphicsLayer;
}
});
});
main.js
require(["esri/config", "esri/Map", "esri/views/MapView", "app/GraphicsManager"
], function(esriConfig, Map, MapView, GraphicsManager) {
esriConfig.apiKey = "";
const map = new Map({
basemap: "arcgis-topographic",
});
const view = new MapView({
map: map,
center: [-81, 41],
zoom: 9,
container: "viewDiv"
});
// ... some logic to get the point data
const gm = new GraphicsManager();
map.add(gm.plotPoint(lat, long, props));
// .. some other logic
});
There you see that the main.js is where the application start, things are set there or in others modules. You know, map, layers, widgets, etc.
Then you have your other code in modules, and you use import them as required.
dojotoolkit - intro to modules

view.goTo map renders blank arcgis

I am working on an arcgis map, I'm trying to update the map center by calling goTo() on my mapview but for some reason the map just changes to be blank and never updates, I am logging the new coordinates and they are correct.
I am using the reference docs here: https://developers.arcgis.com/javascript/latest/api-reference/esri-views-MapView.html
Can someone with some arcgis experience help me out. I know this isn't an issue with my code specifically but it might be an issue with vue and component rendering as it relates to arcgis
so far I have tried
- getting rid of props and updating everything within the component locally
- using keys to force re-render the component
as an interesting note, if I just enter in some magic numbers for my new location the map updates correctly, however when i use some function to get the location and then pass it in, it does not work and just shows as a blank map
my app.vue
<template>
<div id="app">
<web-map v-bind:centerX="lat" v-bind:centerY="long" ref="map"/>
<div class="center">
<b-button class="btn-block" #click="updateCenter()" 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: null,
long: null,
}
},
methods:{
updateCenter(){
this.$refs.map.getLocation()
}
},
};
</script>
my map component
<template>
<div></div>
</template>
<script>
import { loadModules } from 'esri-loader';
export default {
name: 'web-map',
data: function(){
return{
X: -118,
Y: 34,
}
},
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: [-118,34], ///USE PROPS HERE FOR NEW CENTER
zoom: 8
});
});
},
beforeDestroy() {
if (this.view) {
// destroy the map view
this.view.container = null;
}
},
methods:{
showPos(pos){
console.log('new location',pos.coords.latitude,pos.coords.longitude)
this.view.goTo({center:[pos.coords.latitude,pos.coords.longitude]})
},
getLocation(){
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(this.showPos);
} else {
console.log("Geolocation is not supported by this browser.");
}
},
}
};
</script>
Switch:
this.view = new MapView({
container: this.$el,
map: map,
center: [-118,34], ///USE PROPS HERE FOR NEW CENTER
zoom: 8
});
to
this.view = new MapView({
container: this.$el,
map: map });
this.view.center.longitude = -118;
this.view.center.latitude = 34;
this.view.zoom = 8;
The other answer by Tao has the long/latitude backwards in the .goTo({center: []}) method call, which is why it goes to the ocean: https://developers.arcgis.com/javascript/latest/api-reference/esri-views-MapView.html#goTo
Here's something that works:
https://codesandbox.io/s/frosty-glitter-39wpe?file=/src/App.vue
I made it from scratch, only taking small bits from yours and combining them with some examples from ArcGIS (which I'm not familiar with, at all).
One thing to note is that the .goTo({center: [lat, long]}) didn't work as expected: it kept centering in the middle of some ocean.
I then imported Point from esri and passed the center as new Point(long, lat), which seems to produce the expected result. Since it works, I haven't looked further, but I guess it should be doable without the conversion. You probably need to pass in the coordinates system or something along these lines.
As far as I can tell, what's wrong in your example is the way you try to pass data down from parent to child. You expect this.$refs.map to be a Vue instance, but it's not. It's a DOM element. It's basically the Vue instance's $el. Accessing child methods from parent instance is not so straight forward.
Another thing to notice is that, even though you bind centerX and centerY on child in your example, you never seem to use them (but I guess that's just a left over from when you tried with props !?).
Anyways, in my example, I chose to simply update the coords prop of the children while having a watch fn to handle re-centering.

Setting language option in Leaflet.Routing.Control

UPDATED:
I am trying to translate the leaflet routing component with language:'sp'
but it does not work for me.
const createRoutingControl = () => {
L.Routing.control({
router: L.Routing.mapbox(config.features.routing.key),
plan: new (L.Routing.Plan.extend({
createGeocoders: function () {
let container = L.Routing.Plan.prototype.createGeocoders.call(this)
let reverseRoute = createButton('↑↓', container)
let copyToClipboard = createButton('5', container, true)
return container
}
}))([], {
geocoder: geocoder,
language: 'sp'
}),
units: config.features.routing.units,
showAlternatives: true,
autoRoute: true,
routeWhileDragging: true,
}).addTo(map)
}
With " language:'sp' " the form is traslated but not the instruccions.
I know I have to use formatter but I tried to put it in routing.control, routing.plan... (and more places only to test it) and it does not work (the map is not displayed)
The response from #IvanSanchez is almost correct: Control does indeed not have a language option, but several other classes have (not sure this is even properly documented, sorry).
Having said that, as a default, the Control class passes on any option you give it when it instantiates its child components, so passing language as an option to Control will also pass it to the default formatter, the router, etc. The exception is when you provide child components that you instantiate yourself, like the Plan in your example: for that case, you need to explicitly provide the options (like you already do).
So I would assume this code should fix the issue:
const createRoutingControl = () => {
L.Routing.control({
router: L.Routing.mapbox(config.features.routing.key),
plan: new (L.Routing.Plan.extend({
createGeocoders: function () {
let container = L.Routing.Plan.prototype.createGeocoders.call(this)
let reverseRoute = createButton('↑↓', container)
let copyToClipboard = createButton('5', container, true)
return container
}
}))([], {
geocoder: geocoder,
language: 'sp'
}),
units: config.features.routing.units,
showAlternatives: true,
autoRoute: true,
routeWhileDragging: true,
language: 'sp'
}).addTo(map)
I'm sorry to dig up this topic, but I can not translate the instructions in french...
I set the language option 'fr' directly in L.Routing.control and also in the formatter...
I try to debug the lines where the localization is done, and I see my problem at line 16353 of leaflet-routing-machine.js :
formatInstruction: function(instr, i) {
if (instr.text === undefined) {
return this.capitalize(L.Util.template(this._getInstructionTemplate(instr, i),
.....
})));
} else {
return instr.text;
}
where instr.text is already initialized...
If I reset instr.text to "undefined" at debug time, the instruction is well translated...
Do you have any idea of my problem?
My code is below:
$script(['https://unpkg.com/leaflet/dist/leaflet-src.js'], () =>{
$script(['https://unpkg.com/leaflet-routing-machine/dist/leaflet-routing-machine.js'], () => {
this.map = L.map(element);
L.tileLayer('//{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png', {
attribution: 'Map data © OpenStreetMap - rendu OSM France ',
maxZoom: 18
}).addTo(this.map);
this.map = L.map(element);
L.tileLayer('//{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png', {
attribution: 'Map data © OpenStreetMap - rendu OSM France ',
maxZoom: 18
}).addTo(this.map);
let control1 = L.Routing.control(
{
router: L.routing.mapbox(this.key.MapboxKey),
language: 'fr',
formatter: new L.Routing.Formatter({
language: 'fr'
}),
waypoints: [
L.latLng(47.928927, 7.538860),
L.latLng(48.044444, 7.299279),
L.latLng(47.857503, 6.821690),
L.latLng(47.506390, 6.996181),
L.latLng(47.586881, 7.25652)
],
routeWhileDragging: true
})
.on('routingerror', function(e) {
try {
this.map.getCenter();
} catch (e) {
this.map.fitBounds(L.latLngBounds(control1.getWaypoints().map(function(wp) { return wp.latLng; })));
}
handleError(e);
})
.addTo(this.map);
L.Routing.errorControl(control1).addTo(this.map);
====================================================================
As I found the solution in the meantime, I give it away because it seems not to be documented :
I have to add the language option as parameter of the mapbox function, when creating the "router" :
L.routing.mapbox(this.key.MapboxKey, {language: 'fr'}),
This is a case of RTFM.
If you look a bit closer at the API documentation for Leaflet Routing Machine, you'll notice that L.Routing.Control does not have a language option, and that the language option only applies to instances of L.Routing.Formatter, and that a L.Routing.Control can take a formatter option which can hold an instance of L.Routing.Formatter. So putting everything together...
L.Routing.control({
router: L.Routing.mapbox(config.features.routing.key),
formatter: L.Routing.formatter({
language: 'sp'
units: config.features.routing.units,
}),
showAlternatives: true,
autoRoute: true,
// ... etc
},
(P.S.: "prop" is reactjs slang, and that word does not apply here)

Categories

Resources