What I want to do is let the users create a GPX file by selecting some GeoJson features in Leaflet. The way in which I'm doing it is by creating a new GeoJson layer to store the selected features, then converting this to gpx with a plugin called togpx (https://github.com/tyrasd/togpx). Now I have a gpx file, but I don't know how can I let the users download it. Any suggestions? Here's my code:
var GPXfile;
var trails = new L.GeoJSON.AJAX('data/trasee.geojson', {
onEachFeature: function(feature, layer) {
layer.on({
click: function () {
var selectedGeojson = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"name": "Rocka Rolla"
},
"geometry": {
"type": "LineString",
"coordinates": feature.geometry.coordinates
}
}]
}
GPXfile = togpx(selectedGeojson);
}
})
}
}).addTo(map);
A JsFiddle might help: http://jsfiddle.net/pufanalexandru/20ara4qe/1/
You can try that ...
A link to trigger the dowload:
Export to file
Some javascript (you have to include jquery):
$("#exportGPX").on('click', function (event) {
// prepare the string that is going to go into the href attribute
// data:mimetype;encoding,string
data = 'data:application/javascript;charset=utf-8,' + encodeURIComponent(gpxData);
// set attributes href and target in the <a> element (with id exportGPX)
$(this).attr({
'href': data,
'target': '_blank'
});
// let the click go through
});
An example here: http://jsfiddle.net/FranceImage/vxe23py4/
Note: it works with Chrome, but you should try other browsers too.
Related
I'm developing a web application using Mapbox GL, more specifically, its binding for React, react-map-gl.
One of the planned functionalities for the app is adding markers and connecting them.
However, I'm having trouble connecting markers.
I want to start drawing the line when I click on a marker, add a breakpoint to the line when I click elsewhere and finish the line when I click on another marker.
What can I use for this?
I am also working on same, you can use deck.gl for plotting lines on map, or you can also use geoJson for the same.
What I ended up doing was using an EditableGeoJsonLayer with the features for both the markers and the connections between them as follows:
data: {
type: "FeatureCollection",
features: markers.flatMap((marker) => {
// Map markers
let features = [
{
geometry: {
type: "Point",
coordinates: marker.coordinates
},
type: "Feature",
node: marker
}
];
// Map connections
if (marker.connections.length > 0) {
features = features.concat(
marker.connections.flatMap((endMarker) => [
{
geometry: {
type: "LineString",
coordinates: [
marker.coordinates,
endMarker.coordinates
]
},
type: "Feature"
}
])
);
}
return features;
})
}
I have a React function like the following. When a button is clicked, this function places a square on a map of the world at the specified coordinates (see code). However, I want to be able to press a different button and have the square removed. How can I do this? Is there a way to delete things from MapboxGL maps? If so, what function can I use?
The square is rendered using a function from MapboxGL and the web app is made using React JS.
React.useEffect(() => {
if(props.show) {
console.log(props);
var northEast = [131.308594, 46.195042];
var southEast = [117.597656, 8.233237];
var southWest = [79.101563, 32.842674];
var northWest = [86.847656, 44.715514];
// Add bounding box to the map
// Defines bounding box
globalMap.addSource('newroute', {
'type': 'geojson',
'data': {
'type': 'Feature',
'properties': {},
'geometry': {
'type': 'LineString',
'coordinates': [
northEast, southEast, southWest, northWest, northEast
]
}
}
});
// Draws bounding box
globalMap.addLayer({
'id': 'newroute',
'type': 'line',
'source': 'newroute',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': '#ff0000',
'line-width': 5
}
});
}
});
Mapbox-GL has a remove layer method.
// If a layer with ID 'target-layer' exists, remove it.
if (map.getLayer('target-layer')) map.removeLayer('target-layer');
Another way to approach the issue is to keep the old layer, and update the data of the source with the new data (if your use-case allows for it).
"Remove layer" by setting the data of the source to an empty dataset:
map.getSource('target-source').setData({ type: "FeatureCollection", features: [] });
You can then set the data to a populated GeoJSON object if and when you have new data...
I'm creating a new Web application using AngularJS. it consists of a main page with a side menu, whose structure is pre-defined via a JSON file. The JSON would look something like the following (highly simplified!!):
G_Main_Menu = {
"Management":
[{"Command":"DoThis","Label":"Label_DoThis"},
{"Command":"DoThat","Label":"Label_DoThat"}],
"Others":
[{"Command":"DoOther","Label":"Label_DoOther"}]
}
On the other hand, within the HTML page I would be deploying labels extracted from the database (it is a multi-lingual application and hence the contents of the labels would depend on the language selected by the user):
...{{ThisIstheLabelFor_DoThis}}...
...
...{{ThisIstheLabelFor_DoThat}}...
...
...{{ThisIstheLabelFor_DoOther}}...
The JSON as received from the database would look like:
{"Management":
{"Label_DoThis":"This is the explicit contents of label DoThis",
:
"Label_DoThat":"This is the explicit contents of label DoThat",
:
},
"Others":
{"Label_DoOther":"This is the explicit contents of label DoOther"
}
}
So, I have a JSON that contains a string specifying the name of the element contained in a second JSON.
How could I implement such indirect extraction?
Thanks in advance.
You can search for the label in the translation JSON using a function
{{::translate(ThisIstheLabelFor_DoThis, category)}}
where category could be "Management" etc.
And the translate function could be implemented like this:
$scope.translate = function(label_name, category){
return TranslateJson[category][label_name];
}
let gMainMenu = {
"Management": [{
"Command": "DoThis",
"Label": "Label_DoThis"
},
{
"Command": "DoThat",
"Label": "Label_DoThat"
}
],
"Others": [{
"Command": "DoOther",
"Label": "Label_DoOther"
}]
};
let db = {
"Management": {
"Label_DoThis": "This is the explicit contents of label DoThis",
"Label_DoThat": "This is the explicit contents of label DoThat",
},
"Others": {
"Label_DoOther": "This is the explicit contents of label DoOther"
}
};
Object.values(gMainMenu).forEach(menu => {
menu.forEach(item => {
let explicitContent = null;
for (let d of Object.values(db)) {
let key = Object.keys(d).find(k => k === item.Label);
if(key){
explicitContent = d[key];
}
}
item.Label = explicitContent;
});
});
console.log(gMainMenu);
I am trying to integrate CKEditor in Angular App. In CKEditor, I am trying to use uploadimage. In run method of my app I have written following code to listen the events of CKEditor.
CKEDITOR.on( 'instanceCreated', function( event ) {
console.log("CKEditor instance created");
});
CKEDITOR.on( 'fileUploadResponse', function( evt ) {
// Prevent the default response handler.
console.log("Image Uploaded");
evt.stop();
// Ger XHR and response.
var data = evt.data,
xhr = data.fileLoader.xhr,
response = xhr.responseText.split( '|' );
if ( response[ 1 ] ) {
// Error occurred during upload.
data.message = response[ 1 ];
evt.cancel();
} else {
data.url = response[ 0 ];
}
console.log("Image Uploaded");
} );
In console it is printing CKEditor instance created, but not printing Image Uploaded. Somehow it is not listening to fileUploadResponse event.
My config file of CKEditor is as follow:
CKEDITOR.editorConfig = function( config ) {
// Define changes to default configuration here.
// For complete reference see:
// http://docs.ckeditor.com/#!/api/CKEDITOR.config
// The toolbar groups arrangement, optimized for two toolbar rows.
config.toolbarGroups = [
{ name: 'clipboard', groups: [ 'clipboard', 'undo' ] },
{ name: 'editing', groups: [ 'find', 'selection', 'spellchecker' ] },
{ name: 'links' },
{ name: 'insert' },
{ name: 'forms' },
{ name: 'tools' },
{ name: 'document', groups: [ 'mode', 'document', 'doctools' ] },
{ name: 'others' },
'/',
{ name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
{ name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi' ] },
{ name: 'styles' },
{ name: 'colors' },
{ name: 'about' }
];
// Remove some buttons provided by the standard plugins, which are
// not needed in the Standard(s) toolbar.
config.removeButtons = 'Underline,Subscript,Superscript';
// Set the most common block elements.
config.format_tags = 'p;h1;h2;h3;pre';
// Simplify the dialog windows.
config.removeDialogTabs = 'image:advanced;link:advanced';
config.extraPlugins = 'uploadimage';
config.uploadUrl = '/notice/fileupload';
};
Everything is working fine and my image file is also uploading successfully and I am getting following JSON response:
{
"uploaded": 1,
"fileName": "checkout.PNG",
"url": "/img/syllabus/checkout.PNG",
"error": null
}
But fileUploadResponse is not firing after so many tries. I am not sure which part I am missing.
I think the 'fileUploadResponse'-Event has to be registered on the ckeditor-instance and not on CKEDITOR itself.
var editor = $( 'textarea#editor1' ).ckeditor();
editor.on( 'fileUploadResponse', function( evt ) {...});
Thanks, #Benjamin Schüller for pointing in the right direction.
I am using ng-ckeditor library for CKEditor Textarea along with ng-model data. This library has the directive in which they are initiating the CKEditor instance. All I needed is to get that instance and register fileUploadResponse event to it.
Following is my textarea in template html:
<textarea id="noticeDetails" ckeditor="editorOptions" name="description" ng-model="ctrl.notice.description" ></textarea>
And in my Angular Controller, I am defining editorOptions and binding fileUploadResponse:
$scope.editorOptions = {
language: 'en',
allowedContent: true,
entities: false
};
$scope.$on("ckeditor.ready", function( event ) {
var noticeCkEditor = CKEDITOR.instances["noticeDetails"];
noticeCkEditor.on( 'fileUploadResponse', function( evt ) {
// Prevent the default response handler.
evt.stop();
// Get XHR and response.
var data = evt.data,
xhr = data.fileLoader.xhr,
response = xhr.responseText;
var respJson = angular.fromJson(response);
console.log(respJson);
if ( respJson.error ) {
// Error occurred during upload.
data.message = respJson.error.message;
evt.cancel();
} else {
data.url = respJson.url;
}
} );
});
Following is my JSON response on file upload:
{
"uploaded": 1,
"fileName": "IMG_1202.PNG",
"url": "/img/society/notice/IMG_1202.PNG",
"error": null
}
Few things to note here:
You can get an instance after CKEditor completely initialized. ng-ckeditor has broadcast called ckeditor.ready. So on ckeditor.ready you can get an instance and bind events specific to the editor.
CKEditor gives name to the instance using id of the textarea. In my case id is noticeDetails, so it will create an instance with name noticeDetails. In case you have not given the id then it will create the instance with names editor1, editor2 and so on. In my case, I am getting the CKEditor instance with noticeDetails name.
CKEditor documentation has mentioned example code to handle file upload response manually. But it is not working. They are binding whole JSON string to data.message or data.url which is not the way to do as per my experiment. What we need to do is create the JSON object from the response string and appropriately get the message or URL from that JSON object and bind it with data object as shown in the above code.
I am using OpenLayers 3 to animate the paths of migrating animals tagged by scientists. I load the geoJSON file like so
var whaleSource = new ol.source.Vector({
url: 'data/BW2205005.json',
format: new ol.format.GeoJSON()
});
Instead of loading this directly into a layer, I would like to use and reuse the data in the geoJSON file for different purposes throughout my program. For example, I want to pull the lat & lon coordinates into an array to manipulate them to create interpolated animated tracks. Later I will want to query the geoJSON properties to restyle the tracks of males and females.
How might I load the geoJSON data into various arrays at different stages of my program instead of directly into a layer?
Thanks much
When using the url property of ol.source.Vector the class loads the given url via XHR/AJAX for you:
Setting this option instructs the source to use an XHR loader (see ol.featureloader.xhr) and an ol.loadingstrategy.all for a one-off download of all features from that URL.
You could load the file yourself using XHR/AJAX using XMLHttpRequest or a library like jquery which has XHR/AJAX functionality. When you've retreived the GeoJSON collection you can loop over the features array it holds and split it up into what every you need and put those features into new separate GeoJSON collections. Here's a very crude example to give you and idea of the concept:
Assume the following GeoJSON collection:
{
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [0, 0]
},
"properties": {
"name": "Free Willy"
}
}, {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [1, 1]
},
"properties": {
"name": "Moby Dick"
}
}, {
// Etc.
}]
}
Here's how to load it (using jQuery's $.getJSON XHR function) and to split it up in to separate collections:
// Object to store separated collections
var whales = {};
// Load url and execute handler function
$.getJSON('collection.json', function (data) {
// Iterate the features of the collection
data.features.forEach(function (feature) {
// Check there is a whale already with that name
if (!whales.hasOwnProperty(feature.properties.name)) {
// No there isn't create empty collection
whales[feature.properties.name] = {
"type": "FeatureCollection",
"features": []
};
}
// Add the feature to the collection
whales[feature.properties.name].features.push(feature);
});
});
Now you can use the separate collections stored in the whale object to create layers. Note this differs some from using the url property:
new ol.layer.Vector({
source: new ol.source.Vector({
features: (new ol.format.GeoJSON()).readFeatures(whales['Free Willy'], {
featureProjection: 'EPSG:3857'
})
})
});
Here's a working example of the concept: http://plnkr.co/edit/rGwhI9vpu8ZYfAWvBZZr?p=preview
Edit after comment:
If you want all the coordinates for Willy:
// Empty array to store coordinates
var willysCoordinates = [];
// Iterate over Willy's features
whales['Free Willy'].features.forEach(function (feature) {
willysCoordinates.push(feature.geometry.coordinates);
});
Now willysCoordinates holds a nested array of coordinates: [[0, 0],[2, 2]]