Export png from deckl map - mapbox-gl-js

I'm using react-map-gl with deck.gl to load layers into mapbox.
I'm trying to export a png image of the map with layers but when download the image doesn't show the layers.
The layers are: one GeojsonLayer and one HexagonalLayer.
How can show layers in my png??
<MapGL
ref={this.mapRef}
{...viewport}
className="mapbox"
mapStyle={mapStyle}
mapboxApiAccessToken={MAPBOX_TOKEN}
preserveDrawingBuffer={true}
attributionControl={false}
onLoad={this.handleLoad}
onViewportChange={(viewport) => {
viewport.width = "100%";
viewport.height = "400px";
this.setState({ viewport });
}}
>
<DeckGL layers={layers} onHover={this.onHoverDeckgl} viewState={viewport} />
</MapGL>;
// This is my method to download the image.
downloadImage = () => {
const fileName = `${"Mapa"}_${moment().format(
"DD_MM_YYYY_ HH_mm"
)}.png`;
const canvas = this.state.mapGL.getCanvas();
canvas.toBlob(blob => {
FileSaver.saveAs(blob, fileName);
});
};

Finally, I did it!!!
I fould a solution to combine canvas from mapbox and canvas from deckgl.
Aadd id to Deckgl Component and ref
return (
<DeckGL
id="deck-gl-canvas"
ref={this.deckRef}
layers={layers}
onHover={this.onHoverDeckgl}
onLoad={this.handleLoad}
viewState={viewport}
>
{this.renderTooltip()}
</DeckGL>
);
// pass deck reference where will be action to download image
handleLoad = () => {
const deck = this.deckRef.current.deck;
this.props.onGetDeckGL(deck);
};
add ref to Mapbox Component and pass will be the action to download image
Call redraw to deck
Combine canvas on a third canvas
downloadImage = () => {
const fileName = "Map.png";
const { mapGL, deck } = this.state;
const mapboxCanvas = mapGL.getCanvas(
document.querySelector(".mapboxgl-canvas")
);
deck.redraw(true);
const deckglCanvas = document.getElementById("deck-gl-canvas");
let merge = document.createElement("canvas");
merge.width = mapboxCanvas.width;
merge.height = mapboxCanvas.height;
var context = merge.getContext("2d");
context.globalAlpha = 1.0;
context.drawImage(mapboxCanvas, 0, 0);
context.globalAlpha = 1.0;
context.drawImage(deckglCanvas, 0, 0);
merge.toBlob(blob => {
FileSaver.saveAs(blob, fileName);
});
};

Related

Leaflet Layer control same map as base and overlay

I have 3 maps Osm, Lidar and Aerial . I want to select one as the baselayer and one as an overlay. E.g. osm + aerial, osm + Lidar, Aerial + lidar. The LayerControl nearly does what I want but fires baselayerchanged when an overlay is enabled that is also a baselayer optionn. Any ideas on how to sort this would be welcome.
Use two instances of L.Control.Layers, each having a different set of 3 "baselayers".
The nomenclature of "baselayers" and "overlays" can be confusing. As far as a L.Control.Layers is concerned, baselayers are mutually exclusive and overlays are not. You can have something that looks like a graphical overlay but is defined as a baselayer in a control.
Here is the code I ended up with. The important thing is that when changing base layer with an overlay selected, the overlays have to be unloaded to load the new base at the bottom of the z order.
$(document).ready(function () {
var map = L.map('map').setView([-14.0349289249, -171.438639761], 14);
var osm = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: 'Map data © OpenStreetMap contributors'
});
var aerial =L.tileLayer('aerial/{z}/{x}/{y}.png', {
maxZoom: 18,
tms: false,
attribution: 'Aerial from Centre for Samoan Studies (CSS)'
});
var lidar =L.tileLayer('lidar/{z}/{x}/{y}.png', {
maxZoom: 18,
tms: false,
attribution: 'LIDAR from Centre for Samoan Studies (CSS)'
});
// our set of layers
var layers = [osm, aerial, lidar];
// initial base layer
osm.addTo(map)
// to enable base/overlay swapping, add extra properties with unlikely to clash names
osm.isSamoaBase = true;
osm.isSamoaOverlay = false;
osm.samoaName = 'Base';
lidar.isSamoaBase = false;
lidar.isSamoaOverlay = false;
lidar.samoaName = 'LIDAR';
aerial.isSamoaBase = false;
aerial.samoaName = 'Aerial';
aerial.isSamoaOverlay = false;
// set true when the existing base layer is to be replaced
var baseChange = false;
// layer selector for both base and overlays,
// sorted to preserve order as layers move between base and overlay sections
var layerControl = L.control.layers(null, null, {sortLayers:true});
layerControl.addTo(map);
loadLayerControl(layerControl);
map.on('layerremove', function(le){
if (le.layer.isSamoaBase) {
le.layer.isSamoaBase = false;
// a new base will be selected next
baseChange = true;
}
if (le.layer.isSamoaOverlay) {
le.layer.isSamoaOverlay = false;
}
loadLayerControl(layerControl);
});
map.on('layeradd', function(le){
if (baseChange) {
baseChange = false;
// tag as new base
le.layer.isSamoaBase = true;
le.layer.isSamoaOverlay = false;
// restore full base layer opacity
le.layer.setOpacity(1);
}
if (!le.layer.isSamoaBase) {
le.layer.isSamoaOverlay = true;
}
loadLayerControl(layerControl);
//set opacity from slider for any selected overlays
var val = $('#basemapslider').slider("option", "value");
map.eachLayer(function(layer){
if (layer.isSamoaOverlay) {
layer.setOpacity(val);
}
});
});
function loadLayerControl(lc) {
//remove twice in case both layer both a base and overlay option
for (var i = 0; i < layers.length; i++) {
lc.removeLayer(layers[i]);
lc.removeLayer(layers[i]);
}
// must add base first to get base at bottom of z order
for (var i = 0; i < layers.length; i++) {
if (!layers[i].isSamoaOverlay) {
lc.addBaseLayer(layers[i], layers[i].samoaName);
}
}
// finally add overlays
for (var i = 0; i < layers.length; i++) {
if (!layers[i].isSamoaBase) {
lc.addOverlay(layers[i], layers[i].samoaName);
}
}
}
})

Draft.js. How can I update block atomic:image src later on for example article save?

I am having trouble updating my image blocks in editorState in draft.js.
I want to change atomic:image src on button save.
So the src is for example now blob:http://localhost:3000/7661d307-871b-4039-b7dd-6efc2701b623
but I would like to update to src to for example /uploads-from-my-server/test.png
onSave(e) {
e.preventDefault();
const { editorState } = this.state;
const contentState = editorState.getCurrentContent();
editorState.getCurrentContent().getBlockMap().map((block) => {
const type = block.getType();
if (type === 'atomic:image') {
const rangeToReplace = new SelectionState({
anchorKey: block.getKey(),
focusKey: block.getKey(),
});
Modifier.replaceText(contentState, rangeToReplace, '/uploads-from-my-server/test.png');
const newContentState = editorState.getCurrentContent();
this.setState({ editorState: newContentState });
}
return true;
});
I know I can access src string with block.getData().get('src') but I cant set though
Thank you for your awesome editor
I was struggling with a similar problem, I ended up converting the content state to raw array using convertToRaw and then updating it manually and use convertFromRaw and set the new state :
import {EditorState, ContentState, convertToRaw, convertFromRaw /*, ...*/} from 'draft-js';
// ...
onSave(e) {
e.preventDefault();
const { editorState } = this.state;
const contentState = editorState.getCurrentContent();
let rawContent = convertToRaw(contentState);
for(let i = 0; i < rawContent.blocks.length; i++) {
let b = rawContent.blocks[i];
if(b['type'] !== "unstyled" && b.entityRanges.length === 1) {
const entityKey = b['entityRanges'][0]['key'];
const entityMap = rawContent['entityMap'][entityKey];
if(entityMap["type"] === "image") {
rawContent['entityMap'][entityKey]['data']['src'] = '/uploads-from-my-server/test.png';
}
}
}
const newContentState = convertFromRaw(rawContent);
const newEditorState = EditorState.push(this.state.editorState, newContentState, 'update-contentState');
this.setState({editorState: newEditorState});
}
Note: This is not a fully working example, it's just a starting point. Hope it helps :)

Leaflet playback place marker

I am using the playback plugin and i am using it on an image overlay.
https://github.com/hallahan/LeafletPlayback
I need to scale the floor map before placing the marker. with the plugin the marker is placed some where outside of the floor map.
I am able to solve the issue for GPS tracking, where i have written a function to scale the map and place the marker inside pointToLayer method of layer property.
I want to do the same for marker too. any help is appreciated.
const playbackOptions = {
playControl: true,
dateControl: true,
orientIcons: true,
fadeMarkersWhenStale: true,
// layer and marker options
layer: {
pointToLayer(featureData, latlng) {
const { lat, lng } = latlng;
let result = {};
if (featureData && featureData.properties && featureData.properties.path_options) {
result = featureData.properties.path_options;
}
if (!result.radius) {
result.radius = 5;
}
const scaleX = width / details.width;
const scaleY = height / details.length;
const m = {
x: lat * scaleX,
y: lng * scaleY,
};
const iconCls = 'asset-icon';
const item = L.marker(self.map.unproject([m.x, m.y], self.map.getMaxZoom()), {
icon: makeMarker(iconCls, 0),
opacity: 0.9,
type: 'asset',
lat,
lng,
});
item.bindTooltip(`<p>${lat}, ${lng}`, { className: 'asset-label', offset: [0, 0] });
return item;
}
},
marker: {
getPopup(featureData) {
let result = '';
if (featureData && featureData.properties && featureData.properties.title) {
result = featureData.properties.title;
}
return result;
}
}
};
If you retrieve actual GPS coordinates, it would probably be easier to actually do the reverse, i.e. to georeference your image overlay once for good, instead of trying to fiddle with the geographic coordinates of each of the feature you try to show relatively to your image.

How to add an img to L.control.layers?

Is there a way to add an icon/img before the input checkbox inside the layer control?
And is there a way to add a value(or id) prop to the checkbox?
For now I can add an icon with this, but that is not exacltly what I want. Thanks.
L.control.layers({
null
}, {
'<img src="/img/fish.png">Some text':new L.layerGroup(),
}).addTo(map);
This will add an img after the checkbox. Maybe somehow override the _addItem method in the Control.Layers.js, but I don't know how.
Update: Is there a way to add a value prop to the checkbox on this stage?
var layers = L.control.layers({}, {
'name':new L.layerGroup(), // how to add val?
}).addTo(map);
So I can add a value and name(span, label) to the checkbox to get the
<div>
<input type="checkbox" value="some val" class="leaflet-control-layers-selector"><span>name</span>
</div>
Might want to do this with custom JavaScript. I don't believe there is any built-in way to accomplish this. Try something like this:
Save the control layers to a variable:
var layers = L.control.layers({}, {'name' : new L.layerGroup()}).addTo(map);
Get the _overlaysList property (unless you're altering a base map):
var list = layers._overlaysList;
Iterate the input tags:
var inputs = list.getElementsByTagName('input');
Find the one you want to alter, and prepend an image to it.
Well, here is my solution, if someone is interested
//---------------- OVERRIDING THE LAYERS -------------------
L.Control.IconLayers = L.Control.Layers.extend({
initialize: function (baseLayers, overlays, options) {
L.Control.Layers.prototype.initialize.call(this, baseLayers, overlays, options);
},
_addItem: function (obj) {
//console.log("Layer Control:",obj)
var label = document.createElement('label'),
input, icon = false,
checked = this._map.hasLayer(obj.layer);
if (obj.overlay) {
input = document.createElement('input');
input.type = 'checkbox';
input.className = 'leaflet-control-layers-selector';
input.defaultChecked = checked;
input.value = obj.name; // add
console.log(obj)
if ('getIcon' in obj.layer) {
icon = obj.layer.getIcon();
}
} else {
input = this._createRadioElement('leaflet-base-layers', checked);
}
var layer_name = obj.name
if (obj.layer.hasOwnProperty('_options')){
layer_name = obj.layer._options.name
input.id = obj.layer._options.id
}
input.layerId = L.stamp(obj.layer);
L.DomEvent.on(input, 'click', this._onInputClick, this);
var name = document.createElement('span');
name.innerHTML = ' ' + layer_name;
label.appendChild(input);
if (icon) {
var i = document.createElement('span');
i.innerHTML = icon;
label.appendChild(i);
}
label.appendChild(name);
var container = obj.overlay ? this._overlaysList : this._baseLayersList;
container.appendChild(label);
return label;
}
});
L.control.iconLayers = function(baseLayers, overlays, options) {
return new L.Control.IconLayers(baseLayers, overlays, options);
}
L.customLayerGroup = L.LayerGroup.extend({
initialize: function (layers) {
console.log("LAYERS:",layers)
L.LayerGroup.prototype.initialize.call(this, layers);
this._options = layers;
},
});
//---------------- OVERRIDING THE LAYERS -------------------
var layers = L.control.iconLayers({
'Mapbox Streets': L.mapbox.tileLayer('mapbox.streets').addTo(map),
'Mapbox Light': L.mapbox.tileLayer('mapbox.light')
}, {
'1':new L.layerGroup(),
'2':new L.layerGroup(),
'3':new L.customLayerGroup({name:"Boats",id:"3", value:"3"}),
}).addTo(map);

Integrating / adding Google Earth View to my map

I am creating an interactive map for a non profit association "Friends of Knox Mountain Park" but I am getting trouble with the Google Earth view.
I've been searching on the web for weeks and none of the solutions I found works for me. Can someone take a look of the code and let me know what I should do to include Google Earth View in the map? Thanks in advance.
The online project: http://www.virtualbc.ca/knoxmountain/
And this is the javascript file (mapa2.js) containing the google map's code:
google.load('earth', '1');
var map;
var googleEarth;
var gmarkers = [];
var iconShadow = new google.maps.MarkerImage('icons/shadow.png',
new google.maps.Size(46, 42),
new google.maps.Point(0,0),
new google.maps.Point(13, 42));
var sites = [
['Apex Trail - Shelter',49.91174271, -119.48507050, 4, '<img src="images/apex_point_high.jpg">','magenta','14'],
['Apex Trail',49.91286999, -119.48413424, 3, '<img src="images/apex_point_low.jpg">','lemon','1'],
['Gordon Trail',49.91971281, -119.47954356, 2, '<img src="images/apex_point_low.jpg">','lemon','1'],
['Paul Tomb Bay',49.92555541, -119.47710250, 1, '<img src="images/tomb_bay.jpg">','lemon','1']
];
var infowindow = null;
var overlay;
// Used to make Google Map quard coords to MapCruncher/BingMaps quard coords
function TileToQuadKey ( x, y, zoom)
{
var quad = "";
for (var i = zoom; i > 0; i--)
{
var mask = 1 << (i - 1);
var cell = 0;
if ((x & mask) != 0)
cell++;
if ((y & mask) != 0)
cell += 2;
quad += cell;
}
return quad;
}
function init() {
var centerMap = new google.maps.LatLng(49.909671, -119.482241);
var myOptions = {
zoom: 10,
center: centerMap,
mapTypeId: google.maps.MapTypeId.SATELLITE
}
map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
// Create the tile layers
// ASTER Tile Layer
myASTEROptions = {
getTileUrl : function (a,b) {
return "http://www.virtualbc.ca/knoxmountain/map/" + TileToQuadKey(a.x,a.y,b) + ".png";
},
isPng: true,
opacity: 1.0,
tileSize: new google.maps.Size(256,256),
name: "ASTER",
minZoom:13,
maxZoom:20
}
ASTERMapType = new google.maps.ImageMapType( myASTEROptions );
map.overlayMapTypes.insertAt(0, ASTERMapType);
// Aerial Tile Layer
myAerialOptions = {
getTileUrl : function (a,b) {
return "http://www.virtualbc.ca/knoxmountain/map/" + TileToQuadKey(a.x,a.y,b) + ".png";
},
isPng: true,
opacity: 1.0,
tileSize: new google.maps.Size(256,256),
name: "Aerial",
minZoom:15,
maxZoom:21
}
AerialMapType = new google.maps.ImageMapType( myAerialOptions );
map.overlayMapTypes.insertAt(1, AerialMapType);
var panorama = new google.maps.StreetViewPanorama(map.getDiv());
panorama.setVisible(false);
panorama.set('enableCloseButton', true);
map.setStreetView(panorama);
panorama.setPosition(centerMap);
setMarkers(map, sites);
setZoom(map, sites);
infowindow = new google.maps.InfoWindow({
content: "Loading..."
});
googleEarth = new GoogleEarth(map);
google.maps.event.addListenerOnce(map, 'tilesloaded', addOverlays);
}
/*
This functions sets the markers (array)
*/
function setMarkers(map, markers) {
for (var i = 0; i < markers.length; i++) {
var site = markers[i];
var siteLatLng = new google.maps.LatLng(site[1], site[2]);
var marker = new google.maps.Marker({
position: siteLatLng,
map: map,
title: site[0],
zIndex: site[3],
html: site[4],
// Markers drop on the map
animation: google.maps.Animation.DROP,
icon: 'http://www.virtualbc.ca/knoxmountain/icons/icon.png',
shadow: iconShadow
});
gmarkers.push(marker);
google.maps.event.addListener(marker, "click", function () {
infowindow.setContent(this.html);
infowindow.open(map, this);
});
}
}
/*
Set the zoom to fit comfortably all the markers in the map
*/
function setZoom(map, markers) {
var boundbox = new google.maps.LatLngBounds();
for ( var i = 0; i < markers.length; i++ )
{
boundbox.extend(new google.maps.LatLng(markers[i][1], markers[i][2]));
}
map.setCenter(boundbox.getCenter());
map.fitBounds(boundbox);
}
// This function picks up the click and opens the corresponding info window
function myclick(i) {
google.maps.event.trigger(gmarkers[i-1], "click");
}
google.maps.event.addDomListener(window, 'load', init);
The first issue I notice with your site is you are linking to http://www.virtualbc.ca/src/googleearth-compiled.js which does not exist.