Mapbox-gl Hosted S3 - addsource - javascript

I am working on this project where I want to add relatively dynamic data (updating every 15minutes) to a mapbox-gl via a serverless solution. I followed this excellent guide on creating serverless vector tiles and have the base tiles working.
My question is now how would you achieve adding a geojson point layer to the map (hosted on a simple HTML page), using something like:
var map = new mapboxgl.Map({
container: 'map',
style: style: "https://{domain}/bright.json",
});
var url = 'https://{domain}/geojsonfile.json'
map.addSource('geojsonfile', {
'type': 'geojson',
'data': url
});
map.addLayer({
'id': 'geojsonfile',
'type': 'symbol',
'source': 'geojsonfile',
'layout': {
'icon-image': 'rocket-15'
}
});
In a serverless vector environment without using Tippecanoe to convert the json file into protobuf vector tiles and instead adding the layer direct to the map from the javascript file. The above js for addsource and addlayer come from this Mapbox guide.
I can get the above js working when I pass in a mapboxgl.accessToken instead of self-hosting; however that's as far as I have been able to get. The geojson file is hosted in a CORS enabled s3 bucket.
Is the issue with loading in the source or displaying the layer? I have also tried modifying the bright.json file to handle the source and layer so that I only have to replace the s3 file, however have had no luck.
Any and all help / suggestions are much appreciated.

After quite a lot of blind trial and error, finally figured out a solution. Turns out my original script was trying to load the layer to the map on-load. However, the tile base load is configured via the style and tile files pre-load.
Therefore, the js addSource needs to be added to the map post-load and then style on-load.
this.map.on('style.load', function () {
this.pastInitialLoad = true;
this.map.addSource("geojsonfile", {
"type": "geojson",
"data": url }
);
If anyone else is stuck, these guides / sources were of great help!
https://schwanksta.com/blog/vector-tiles-introduction-pt1
http://fuzzytolerance.info/blog/2016/03/16/Leaflet-to-Mapbox-GL/
https://github.com/mapbox/mapbox-gl-js/issues/2268

Related

Rails 7 importmap leaflet-css images path fix?

What I'd like to do:
I would like to have a leaflet layers control in my Rails 7 app.
What I get:
Leaflet works fine, but the control appears without the icon, like this:
and in the browser console I get this error:
GET http://my_ip/trial_models/undefined/layers.png 404 (Not Found)
and in my server log the error is presented like this:
Started GET "/trial_models/undefined/layers.png" for my_ip at 2022-03-30 06:50:43 +0000
ActionController::RoutingError (No route matches [GET] "/trial_models/undefined/layers.png"):
I've replaced the actual ip address with "my_ip". The 'undefined' is really there in the error messages.
How I did it:
First I created a Rails 7 app and imported leaflet like this:
./bin/importmap pin leaflet
./bin/importmap pin leaflet-css
I then created a TrialModel using standard rails g scaffold. I then added the following stimulus div in trail_models\show.html.erb :
<div data-controller="trialmap" data-trialmap-target="trial" style="height:600px" class="leaflet-container"></div>
I created \app\javascript\controllers\trialmap_controller.js with the following contents:
import { Controller } from "#hotwired/stimulus";
import "leaflet-css";
export default class extends Controller {
static targets = [ "trial" ]
connect(){
import("leaflet").then( L => {
this.map = L.map(this.trialTarget).setView([ 51.472814, 7.321673 ], 14);
var base_map = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap | SwmmGo',
transparency: true,
opacity: 0.5
}).addTo(this.map);
var landMap = L.tileLayer('http://{s}.tile.thunderforest.com/landscape/{z}/{x}/{y}.png', {attribution: 'attributions'});
var layersControl = new L.Control.Layers({
"Street Map": base_map,
"Landscape": landMap
});
this.map.addControl(layersControl);
});
}
disconnect(){
this.map.remove()
}
}
What I've explored:
I tried downloading leaflet-css like this:
./bin/importmap pin leaflet-css --download
but same result.
I can see that there is a references background-image:url(images/layers.png); in the imported/ downloaded file vendor/javascript/leaflet-css.js. But there doesn't seem be any images folder anywhere like vendor/javascript/images/ or the like.
The stringify version of L.Control.prototype.options is just {"position":"topright"}.
I tried setting L.Control.prototype.options.iconUrl = "layers.png" (and "/layers.png") in the js controller and then placing a manually downloaded layers.png file in public. But this changed neither the results nor the error messages.
My questions:
How can I get the icon to show and the error to disappear? (a work-around would be ok too)
What is the "correct way" to handle assets and asset paths which css files reference when using importmap?
Any help would be much appreciated! Thanks!
I had similiar problems when using markers, my solution was not using leaflet-css and just using the leaflet CSS from CDN
I did like Luis - put the leaflet.css downloaded from https://leafletjs.com in vendor/assets/stylesheets and the images in public/images - that solved my issues with broken marker/layer images
Don't use leaflet-css, it bundles both the leaflet css and an ancient version (0.6.4) of the leaflet js code. It seems that the other answers here are on target - rails importmaps are for js only; if you are working with a third-party lib that has its own css assets you will need to handle those separately either through a CDN or by downloading them and placing them alongside your other css assets.

Adding a Shapes files as a Layer on Mapbox Map

I have a shapes file from here. I wish to add it on a Map, similar to what is done on the web page. I am not quite sure how to proceed with it. I want to use it on Web and hence use JavaScript.
Someone suggested me to use the shapes file as a Layer on the map. But how to go about it? Anyone experience with Mapbox, Leaflet or OSM please guide.
I was finally able to add the shapes file by uploading it as a tileset to Mapbox Studio.
It provided me with a map Id and source-layer name which I was able to add as a Layer on my Mapbox map as follows:
map.addLayer({
id: '<id>',
type: 'fill',
source: {
type: 'vector',
url: 'mapbox://<map-id>' // Mapbox tileset Map ID
},
'source-layer': '<layer-name>', // name of tilesets
'paint': {
'fill-color': '#088',
'fill-opacity': 0.8,
'fill-outline-color': '#000'
}
});
In order to load a SHP file and display it on a Leaflet / Mapbox.js map, you have several Leaflet plugins that can make the task easy.
For example leaflet.shapefile (online demo where you can drop your zipped SHP and DBF files).
The data source that you mention also provides KML format, which may be easier to use.
For KML, you can use for example leaflet-omnivore:
universal format parser for Leaflet & Mapbox.js
var map = L.mapbox.map('map', 'mapbox.streets')
.setView([38, -102.0], 5);
omnivore.kml('a.kml').addTo(map);
Live example with Leaflet and leaflet-omnivore: https://plnkr.co/edit/KVXqBScBuIrAahg4VsGi?p=preview

How to load a custom .pbf file in ngx-openlayers

I was able to load maps using the ngx-openlayers with the following code
<aol-map [width]="'100%'" [height]="'100%'">
<aol-view [zoom]="zoom">
<aol-coordinate [x]="5" [y]="45" [srid]="'EPSG:4326'"></aol-coordinate>
</aol-view>
<aol-layer-tile [opacity]="opacity">
<aol-source-osm></aol-source-osm>
</aol-layer-tile>
<aol-layer-vector [opacity]="opacity">
<aol-source-vector>
<aol-feature>
<aol-geometry-point>
<aol-coordinate [x]="5" [y]="45" [srid]="'EPSG:4326'"></aol-coordinate>
</aol-geometry-point>
<aol-style>
<aol-style-circle [radius]="10">
<aol-style-stroke [color]="'black'" [width]="width"></aol-style-stroke>
<aol-style-fill [color]="'green'"></aol-style-fill>
</aol-style-circle>
</aol-style>
</aol-feature>
<aol-feature>
<aol-geometry-point>
<aol-coordinate [x]="5.1" [y]="45.1" [srid]="'EPSG:4326'"></aol-coordinate>
</aol-geometry-point>
<aol-style>
<aol-style-icon [src]="'assets/marker.png'" [anchor]="[0.5, 1]" [anchorXUnits]="'fraction'" [anchorYUnits]="'fraction'" [scale]="0.1"
[anchorOrigin]="'top-left'">
</aol-style-icon>
</aol-style>
</aol-feature>
</aol-source-vector>
</aol-layer-vector>
</aol-map>
My issue is I want to load a custom map location that I have served on my local machine using MapTile server on http://localhost:8090/tileserver.php#trails/ol3 URL.
Now the XYZ URL from the above server is (http://localhost:8090/tileserver.php?/index.json?/trails/{z}/{x}/{y}.pbf)
How can I load this map using the ngx-openlayers packkage
I have tried to use this but it is not working
<aol-map [width]="'500px'" [height]="'300px'">
<aol-interaction-default></aol-interaction-default>
<aol-control-defaults></aol-control-defaults>
<aol-control-fullscreen></aol-control-fullscreen>
<aol-layer-tile>
<aol-source-osm></aol-source-osm>
<aol-source-xyz [url]="'http://localhost:8090/tileserver.php?/index.json?/trails/{z}/{x}/{y}.pbf'">
</aol-source-xyz>
</aol-layer-tile>
<aol-view [zoom]="12">
<aol-coordinate [x]="6.47" [y]="53.44" [srid]="'EPSG:4326'"></aol-coordinate>
</aol-view>
</aol-map>
So yeah I'm about to answer my own question cause it might help someone in the future.
The Basics
There are two broad classifications for maps from OSM (OpenStreetMap)
1.Raster Format: This one is the raster image (PNG, JPEG, SVG ...) tiles for the map. This format can be compressed and put it to .mbtiles format.
You can use tools like mbuilt to extract the contents into the tms, wsm or XYZ folder structure. This one is the easy and faster option.
Vector Format: This one is a Vector file format it can have multiple file type like .pbf inside it. It can also be compressed into a .mbtiles file. we need a tile server to render the contents of the vector files.
Tile server GL is a tile server to serve both vector/raster map files (mbtiles)
After solving the offline hosting issue, I have used a default leaflet.js package for angular and everything worked fine without an issue.
I used OpenMaptiles map files to host them using the tileserver-gl

OpenLayers Map BoundingBox calculation

I'm using the GeoServer to hold some maps, and just a simple OpenLayers app to load and show the data (for now).
I'm successfully loading the demo data (which is in WGS84), but when it comes to my data (which is in Balkans Zone 7, EPSG:31277), when I look at the request, it seems like the BBOX is completely out of order.
I checked the BBOX from the GeoServer preview page (which is made with openLayers) and it looks like this, and works:
http://127.0.0.1:2113/geoserver/GISHome/wms?LAYERS=GISHome%3ANis11Katastar&STYLES=&FORMAT=image%2Fjpeg&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&SRS=EPSG%3A31277&BBOX=7572000,4796000,7574000,4798000&WIDTH=512&HEIGHT=512
The only thing that's different with my request is the BBOX. When copying the BBOX into my request, it works.
http://127.0.0.1:2113/geoserver/wms?LAYERS=Nis11Katastar&FORMAT=image%2Fpng&WIDTH=256&HEIGHT=256&PROJECTION=EPSG%3A31277&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&STYLES=&SRS=EPSG%3A4326&BBOX=-180,-90,0,90
In the GeoServer source, the bounds are somehow calculated and hardcoded. The question is, how? Is there a way not to hardcode them? How should I calculate them. I've tried adding bounds, maxExtent, resolution, I'm obviously missing something more than that here. The GeoServer works fine, I'm using the layers from QuantumGIS.
Ext.onReady(function() {
var map = new OpenLayers.Map();
var layer = new OpenLayers.Layer.WMS(
"Global Imagery",
"http://127.0.0.1:2113/geoserver/wms",
{
LAYERS:'Nis11Katastar',
format: 'image/png',
width:600,
height:400,
projection: new OpenLayers.Projection("EPSG:31277"),
}
);
Thankyou.
Oh, yes, I'm using GeoExt, but that doesn't change much.
It seems solved. I was setting the right properties, but to the WMS layer object, not to the map object. As for the bounds question, I'm just copying the bounds from the geoserver control panel.

Google Maps API and KML File LocalHost Development Options

The Google Maps JavaScript version 3 API library documentation clearly explains:
The Google Maps API supports the KML
and GeoRSS data formats for displaying
geographic information. These data
formats are displayed on a map using a
KmlLayer object, whose constructor
takes the URL of a publicly accessible
KML or GeoRSS file.
There are even several Stack Overflow questions about how to load local data:
Loading a local .kml file using google maps?
Google Maps kml files
Some of the answers have pointed to third party libraries which can parse KML locally without the file needing to be public:
geoxml3
GeoXML
EGeoXml
And while these solutions are good if you have a need to keep your data private, I simply want to make development easier. When running locally I obviously cannot parse my KML and therefore lose functionality that I am trying to test. I've posted a single generic KML file on a publicly available site, but then have to have different development code to render one thing vs. something else when running for real.
What are my options for local development to render what would be publicly available dynamically generated KML files?
It seems like you've outlined the options pretty well:
If you want to work with local data, without involving a publicly accessible webserver, you'll need to use a javascript-based approach to parse the KML and load it onto the map. While this won't perfectly replicate the Google functionality, it is likely good enough for initial development if you only care about displaying the KML features. In this case, I'd probably set up a stub class, like this:
// I'll assume you have a global namespace called MyProject
MyProject.LOCAL_KML = true;
MyProject.KmlLayer = function(url) {
// parse the KML, maybe caching an array of markers or polygons,
// using one of the libraries you list in your question
};
// now stub out the methods you care about, based on
// http://code.google.com/apis/maps/documentation/javascript/reference.html#KmlLayer
MyProject.KmlLayer.prototype.setMap = function(map) {
// add the markers and polygons to the map, or remove them if !map
}
// etc
Now either put a switch in the code, or comment/uncomment, or use a build script to switch, or whatever your current process is for switching between dev and production code:
var kmlPath = "/my.kml";
var kmlLayer = MyProject.LOCAL_KML ?
new MyProject.KmlLayer(MyProject.LOCAL_KML_HOST + kmlPath) :
new google.maps.KmlLayer(MyProject.PRODUCTION_KML_HOST + kmlPath);
kmlLayer.setMap(myMap);
If, on the other hand, you need all of the functionality in the Google KmlLayer, or you want to make sure things work with the production setup, or you don't want to bother stubbing out the functionality Google provides, then you'll need to upload it to a publicly available server, so that Google can do its server-side processing.
Aside from the obvious options (FTP, a command-line script to upload your new KML file, etc), most of which require you to do something manually before you load your map page, you might consider building the update into the page you're loading. Depending on the platform you're using, this might be easier to do on the back-end or the front-end; the key would be to have a script on your public server that would allow the KML to be updated:
Get KML string from request.POST
Validate the KML string (just so you aren't opening your server to attacks)
Write to a single file, e.g. "my.kml"
Then, when you view your map page, update the remote KML based on the data from localhost. Here's a client-side version, using jQuery:
// again, you'd probably have a way to kill this block in production
if (MyProject.UPDATE_KML_FROM_LOCALHOST) {
// get localhost KML
$.get(MyProject.LOCAL_KML_HOST + kmlPath, function(data) {
// now post it to the remote server
$.post(
MyProject.DEV_KML_HOST + '/update_kml.php',
{ kml: data },
function() {
// after the post completes, get the KML layer from Google
var kmlLayer new google.maps.KmlLayer(MyProject.DEV_KML_HOST + kmlPath);
kmlLayer.setMap(myMap);
}
);
})
}
Admittedly, there are a lot of round-trips here (page -> localhost, page -> remote server, Google -> remote server, Google -> page), so this is going to be slow. But it would allow you to have Google's code properly render dynamic KML data produced on localhost, without having to take a separate manual step every time you reload the page.
Definitely, Google Maps KmlLayer is designed for you to send your data to them.
https://developers.google.com/maps/documentation/javascript/kml
Have a look the following log.
//console
var src = 'https://developers.google.com/maps/documentation/javascript/examples/kml/westcampus.kml';
var kmlLayer = new google.maps.KmlLayer(src, {
suppressInfoWindows: true,
preserveViewport: false,
map: your_gmap_object
});
Creating Marker, Polygon, they are all browser side parsing and rendering.
As you can see from next network log, KmlLayer class send source URL to Google Server to parse it and (do something in their end) and send the parsed result back to your browser to render.
//REQUEST from browser
https://maps.googleapis.com/maps/api/js/KmlOverlayService.GetOverlays?1shttps%3A%2F%2Fdevelopers.google.com%2Fmaps%2Fdocumentation%2Fjavascript%2Fexamples%2Fkml%2Fwestcampus.kml&callback=_xdc_._lidt3k&key=AIzaSyBeLTP20qMgxsQFz1mwLlzNuhrS5xD_a_U&token=103685
//RESPONSE from google server
/**/_xdc_._lidt3k && _xdc_._lidt3k( [0,"kml:cXOw0bjKUSmlnTN2l67v0Sai6WfXhSSWuyNaDD0mAzh6xfi2fYnBo78Y2Eg","|ks:;dc:tg;ts:51385071|kv:3|api:3",...
["KmlFile"],[[37.423017,-122.0927],[37.424194,-122.091498]],[["g74cf1503d602f2e5"],["g58e8cf8fd6da8d29"],["ge39d22e72437b02e"]],1,[["client","2"]],-21505,[["ks",";dc:tg;ts:51385071"],["kv","3"],["api","3"]]] )
As #capdragon mentioned above, it would be better parse KML by yourself.
UPDATE
Here is compact KML parser code.
This only for google.maps Marker and Polygon.
html
<input type='file' accept=".kml,.kmz" onchange="fileChanged()">
script, I used typescript but it is pretty same with javascript
file: any
fileChanged(e) {
this.file = e.target.files[0]
this.parseDocument(this.file)
}
parseDocument(file) {
let fileReader = new FileReader()
fileReader.onload = async (e: any) => {
let result = await this.extractGoogleCoords(e.target.result)
//CREATE MARKER OR POLYGON WITH result here
console.log(result)
}
fileReader.readAsText(file)
}
async extractGoogleCoords(plainText) {
let parser = new DOMParser()
let xmlDoc = parser.parseFromString(plainText, "text/xml")
let googlePolygons = []
let googleMarkers = []
if (xmlDoc.documentElement.nodeName == "kml") {
for (const item of xmlDoc.getElementsByTagName('Placemark') as any) {
let placeMarkName = item.getElementsByTagName('name')[0].childNodes[0].nodeValue.trim()
let polygons = item.getElementsByTagName('Polygon')
let markers = item.getElementsByTagName('Point')
/** POLYGONS PARSE **/
for (const polygon of polygons) {
let coords = polygon.getElementsByTagName('coordinates')[0].childNodes[0].nodeValue.trim()
let points = coords.split(" ")
let googlePolygonsPaths = []
for (const point of points) {
let coord = point.split(",")
googlePolygonsPaths.push({ lat: +coord[1], lng: +coord[0] })
}
googlePolygons.push(googlePolygonsPaths)
}
/** MARKER PARSE **/
for (const marker of markers) {
var coords = marker.getElementsByTagName('coordinates')[0].childNodes[0].nodeValue.trim()
let coord = coords.split(",")
googleMarkers.push({ lat: +coord[1], lng: +coord[0] })
}
}
} else {
throw "error while parsing"
}
return { markers: googleMarkers, polygons: googlePolygons }
}
output
markers: Array(3)
0: {lat: 37.42390182131783, lng: -122.0914977709329}
...
polygons: Array(1)
0: Array(88)
0: {lat: -37.79825999283025, lng: 144.9165994157198}
...

Categories

Resources