Get extent from KML layer in ArcGIS Online - javascript

Is there any way to calculate the extent of a KML layer loaded from the web using the KMLLayer({ url: "my file" }) method in ArcGIS Online? The KMLs loaded from AGOL have a valid fullExtent property, but ones loaded from other sources seem to default to the entire world, which is not useful.
Here is an example:
app.kml=new KMLLayer({ url: "my file" });
app.map.add(app.kml);
app.kml.load().then(function() { app.mapView.extent=app.kml.fullExtent; console.log(app.kml) });
It is live at:
http://viseyes.org/visualeyes/test.htm?kml=https://www.arcgis.com/sharing/rest/content/items/a8efe6f4c12b462ebedc550de8c73e22/data
The console prints out the KMLLayer object, and the fullExtent field seems to be not set right.

I agree, it does not seem like the fullExtent property is what you would expect. I think there are two workarounds:
Write some code to query the layerView to get the extent:
view.whenLayerView(kmlLayer).then(function(layerView) {
watchUtils.whenFalseOnce(layerView, "updating", function() {
var kmlFullExtent = queryExtent(layerView);
view.goTo(kmlFullExtent);
});
});
function queryExtent(layerView) {
var polygons = layerView.allVisiblePolygons;
var lines = layerView.allVisiblePolylines;
var points = layerView.allVisiblePoints;
var images = layerView.allVisibleMapImages;
var kmlFullExtent = polygons
.concat(lines)
.concat(points)
.concat(images)
.map(
graphic => (graphic.extent ? graphic.extent : graphic.geometry.extent)
)
.reduce((previous, current) => previous.union(current));
return kmlFullExtent;
}
Example here.
-- or --
Call the utility service again and use the "lookAtExtent" property:
view.whenLayerView(kmlLayer).then(function(layerView) {
watchUtils.whenFalseOnce(layerView, "updating", function() {
// Query the arcgis utility and use the "lookAtExtent" property -
esriRequest('https://utility.arcgis.com/sharing/kml?url=' + kmlLayer.url).then((response) => {
console.log('response', response.data.lookAtExtent);
view.goTo(new Extent(response.data.lookAtExtent));
});
});
});
Example here.

Related

Toggle GeoJSON layer in leaflet sidebar

How can I toggle the GeoJSON layers in my leaflet map as I would the L.marker layers?
https://jsfiddle.net/mawr35vj/
Please pardon me if this is a simple question, I'm still new to leaflet and have spent all day on this.
Here is the GeoJSON I would like toggled in the sidebar.
fetch('https://data.cityofnewyork.us/resource/ek5y-zcyf.geojson?$where=latitude is not null')
.then(function (response) {
// Read data as JSON
return response.json();
})
.then(function (data2) {
// Create the Leaflet layer for the data
var complaintLayer = L.geoJson(data2, {
// We make the points circles instead of markers so we can style them
pointToLayer: function (geoJsonPoint, latlng) {
return L.circleMarker(latlng);
},
// Then we can style them as we would other features
style: function (geoJsonFeature) {
return {
fillColor: '#0000ff',
radius: 6,
fillOpacity: 0.7,
stroke: false
};
}
});
});
-I tried assigning it a "var"
-I tried adding "complaintLayer" in the overlays as I did with the L.marker
-And many other various things that I can't remember but is obviously not working...
Update:
I'm trying to load the GeoJSON and assign it a variable, but having difficulty. I'm looking at this and related threads: How can I assign the contents of a geojson file to a variable in Javascript?
I got it to work if I just copy and paste the GeoJSON into the script, but having difficulty if I want to load it from a local file or API.
You can put complaintLayer in an array for a marker control, but the variable must be in the right scope - from the code you've posted it looks like it's local to the function it's populated in, so it won't be visible outside.
Per peeebeee's suggestion, I fixed the issue by loading the data and putting them into a "promise."
you can see a working example below:
https://jsfiddle.net/4x3sk1va/
Example of a promise below (taken from https://glitch.com/#ebrelsford)
// Fetch collisions data from our Glitch project
var collisionsFetch = fetch('https://cdn.glitch.com/03830164-a70e-47de-a9a1-ad757904d618%2Fpratt-collisions.geojson?1538625759015')
.then(function (response) {
// Read data as JSON
return response.json();
});
// Fetch lanes data from our Glitch project
var lanesFetch = fetch('https://cdn.glitch.com/fcedf615-7fef-4396-aa74-2e03fc324d71%2Fpratt-bike-routes.geojson?1538628711035')
.then(function (response) {
// Read data as JSON
return response.json();
});
// Once both have loaded, do some work with them
Promise.all([collisionsFetch, lanesFetch])
.then(function (fetchedData) {
console.log('Both datasets have loaded');
// Unpack the data from the Promise
var collisionsData = fetchedData[0];
var laneData = fetchedData[1];
// Add data in the order you want--first goes on the bottom
L.geoJson(collisionsData).addTo(map);
L.geoJson(laneData).addTo(map);
});

[Autodesk Forge viewer]How to get the dbIds of the objects in a specified layer

I have a SVF file translated from 2d DWG and successfully loaded in a Viewer.
Now I want to query attributes/properties of some objects in a layer.
Here is what I've done so far:
let layer = viewer.model.getLayersRoot().children.find(x=> x.name==='Marker');//find the layer named by 'Marker'----{name: "Marker", index: 72, id: 71, isLayer: true}
let objectTree = viewer.model.getData().instanceTree;//get the Object Tree and its One-dimensional array of dbIdList
// stuck here
// looking for some method like objectTree.getIdListInLayer(layerId)
Any suggestion is appreciated.
Unfortunately, it might not be possible to do this currently. Please refer this post:
How to get a list of dbids contained in a layer?
According to Eason Kang's answer, there is no official approach to achieve this. So the only way left is to iterate the dbIdList. Here is the code:
function query(dbId, model, layerName) {
if (!dbId) return Promise.resolve(null);
return new Promise(resolve => {
model.getProperties(dbId, x => {
let layerProp = x.properties.find(x => x.displayName === 'Layer' && x.displayValue === layerName);
resolve(!!layerProp ? x : null);
});
});
}
Promise.all(Object.keys(objectTree.nodeAccess.dbIdToIndex).map(dbId => query(dbId = dbId - 0, viewer.model, layerName = 'Marker')))
.then(function(resultList) {
resultList = resultList.filter(x => !!x);
console.table(resultList); //this is all the objects in the Marker layer
});

Using idb instead of localStorage

I'm following the Progressive Web App lab from Google and it says that it's using localStorage for simplicity but that we should change it to idb.
Basically, we want to store a list of cities to display their weather information.
I tried using plain idb following the info here but I think I'm too new to this and I couldn't get any of this. Am I supposed to do:
const dbPromise = idb.open('keyval-store', 1, upgradeDB => {
upgradeDB.createObjectStore('keyval');
});
and would keyval be the name of my variable where I would use keyval.get() or keyval.set() to get and store values?
I decided to move on to the simpler idbKeyval, I'm doing:
app.saveSelectedCities = function() {
var selectedCities = JSON.stringify(app.selectedCities);
idbKeyval.set(selectedCities);
};
instead of the localStorage example:
app.saveSelectedCities = function() {
var selectedCities = JSON.stringify(app.selectedCities);
localStorage.selectedCities = selectedCities;
};
and
app.selectedCities = idbKeyval.keys().then(keys => console.log(keys)).catch(err => console.log('It failed!', err));
instead of the localStorage example:
app.selectedCities = localStorage.selectedCities;
But my app is not loading any data, and in the developer tools console, I get:
app.js:314 Uncaught ReferenceError: idbKeyval is not defined(…)
I'm sure I'm missing something trivial but these are my first steps with javascript and the likes, so please, any help with any of the points touched here would be greatly appreciated!
Given the error you're seeing, it looks like you've forgotten to include the idb-keyval library.
I too was going through this, and wanted it to work with localForage. Took a bit, because I'm new to it too, but here is what I used for the save and load functions which made it all work.
// TODO add saveSelectedCities function here
// Save list of cities to localStorage
app.saveSelectedCities = function() {
var selectedCities = JSON.stringify(app.selectedCities);
localforage.setItem('selectedCities', selectedCities);
//localStorage.selectedCities = selectedCities;
}
localforage.getItem('selectedCities').then(function(cityList) {
app.selectedCities = cityList;
app.selectedCities.forEach(function(city) {
app.getForecast(city.key, city.label);
});
}).catch(function(err) {
app.updateForecastCard(initialWeatherForecast);
app.selectedCities = [
{key: initialWeatherForecast.key, label: initialWeatherForecast.label}
];
app.saveSelectedCities();
});

Confused with Viewer API vs example, passing options to extensions

I am confused as the examples on how to use the Viewer don't seem to match the documentation of the API, some functions are not in the docs or their signature is different.
Base on the examples code, how do I pass options to the extensions I instantiate? I would like to pass my extension a callback.
Thanks!
We need to fix the doc so it does not rely anymore on the undocumented A360 viewer additional code, which is supposed to be internal. Sorry for the incovenience, we will do this asap...
For the time being, you can use the code from my viewer boilerplate sample:
function initializeViewer(containerId, urn) {
Autodesk.Viewing.Document.load(urn, function (model) {
var rootItem = model.getRootItem();
// Grab all 3D items
var geometryItems3d = Autodesk.Viewing.Document.getSubItemsWithProperties(
rootItem,
{ 'type': 'geometry', 'role': '3d' },
true);
// Grab all 2D items
var geometryItems2d = Autodesk.Viewing.Document.getSubItemsWithProperties(
rootItem,
{ 'type': 'geometry', 'role': '2d' },
true);
var domContainer = document.getElementById(containerId);
//UI-less Version: viewer without any Autodesk buttons and commands
//viewer = new Autodesk.Viewing.Viewer3D(domContainer);
//GUI Version: viewer with controls
viewer = new Autodesk.Viewing.Private.GuiViewer3D(domContainer);
viewer.initialize();
viewer.setLightPreset(8);
//Button events - two buttons to load/unload a sample extension
// Irrelevant to viewer code itself
var loadBtn = document.getElementById('loadBtn');
loadBtn.addEventListener("click", function(){
loadExtension(viewer);
});
var unloadBtn = document.getElementById('unloadBtn');
unloadBtn.addEventListener("click", function(){
unloadExtension(viewer);
});
// Illustrates how to listen to events
// Geometry loaded is fired once the model is fully loaded
// It is safe to perform operation involving model structure at this point
viewer.addEventListener(
Autodesk.Viewing.GEOMETRY_LOADED_EVENT,
onGeometryLoaded);
//optional
var options = {
globalOffset: {
x: 0, y: 0, z: 0
}
}
// Pick the first 3D item ortherwise first 2D item
var viewablePath = (geometryItems3d.length ?
geometryItems3d[0] :
geometryItems2d[0]);
viewer.loadModel(
model.getViewablePath(viewablePath),
options);
}, function(err) {
logError(err);
});
}
Once the viewer is initialized, you can load independently each extension and pass a callback as follow:
var options = {
onCustomEventFiredByMyExtension: function() {
console.log('LMV rulez!')
}
}
viewer.loadExtension('MyExtensionId', options)
But I think a more elegant approach would be to fire events from the extension itself, which may look like this:
viewer.loadExtension('MyExtensionId')
var myExtension = viewer.getExtension('MyExtensionId')
myExtension.on('CustomEvent', function () {
console.log('LMV still rulez!')
})
See micro-events for a super simple event library.

migrating from php to node : asynchronous in loop

I'm getting in trouble to migrate my website from PHP to Node.js.
I'm actually using SQLite3 along with the Spatialite extension to show some layers on an OpenLayers map. Users choose which layers they want to show using checkboxs in a menu [ thematics => rubrics => layers ], something like :
MY THEMATIC
MY RUBRIC
ONE LAYER
ANOTHER LAYER
STILL ANOTHER LAYER
ANOTHER RUBRIC ...
ANOTHER THEMATIC ...
A JSON file describes the content of this menu :
{
"MY THEMATIC": {
"MY RUBRIC": [
"ONE LAYER", "ANOTHER LAYER", "STILL ANOTHER LAYER"
],
"ANOTHER RUBRIC": ...
},
"ANOTHER THEMATIC"
}
Each layer is linked to another JSON file describing the layer ( layer and legend color, which table in the database, stroke width, etc... )
When generating the menu, I have to check if layers are valid ( database exists, table exists, query must return at least one row ! ).
If a layer is not valid, it isn't shown in the menu.
If all layers in a rubric are not valid, the rubric doesn't appear in the menu.
If all rubrics are not showing any layer, the thematic doesn't appear too.
My problem is that I'm actually using SQLite3 in a blocking way in PHP but database queries are asynchronous in Node.js. So I've tried to do this with callbacks, working, but code is really ugly and difficult to read/understand. I looked at promises but I didn't get it to work. Here is the algorithmic PHP version of my source code generating the menu :
$out = Array(); // Output array to fill in
$thematics = json_decode("..."); // My JSON file read and stored in a PHP array
foreach ($thematics as $thematicName => $rubrics) {
$out[$thematicName] = Array();
$flagThematic = true; // Flag controlling if the thematic contains at least one rubric
foreach ($rubrics as $rubricName => $layers) {
if ($rubricName == "rubric name to exclude") {
continue;
}
$out[$thematicName][$rubricName] = Array();
$flagRubric = true; // Flag controlling if the rubric contains at least one valid layer
foreach ($layers as $layer) {
// isValid
// This function query the database to check the layer validity
// THIS is the asynchronous one in node.js
if (!isValid($layer)) {
continue;
}
$out[$thematicName][$rubricName][] = ["..."]; // add layer info such as legend, name ...
$flagRubric = false;
}
if ($flagRubric === true) {
// No layer in this rubric, deleting the rubric
unset($out[$thematicName][$rubricName]);
} else {
$flagThematic = false;
}
}
if ($flagThematic === true) {
// No rubric in this thematic, deleting the thematic
unset($out[$thematicName]);
}
}
return $out;
Do you have any advice to help me to turn this code into Node.js ?
Thanks in advance.
EDIT
I tried something like that, using bluebird (https://github.com/petkaantonov/bluebird) :
function generateMenu(callback) {
var out = {};
Promise.map(Object.keys(thematics), function (thematicName) {
out[thematicName] = {};
return {
rubrics: thematics[thematicName],
thematicName: thematicName
};
}).map(function (res) {
Promise.map(Object.keys(res.rubrics), function (rubricName) {
if (rubricName === "rubric name to exclude") {
return null; // don't really know how to do here, I think I'm doing it wrong
}
out[res.thematicName][rubricName] = {};
return {
layers: them[res.thematicName][rubricName],
thematicName: thematicName,
rubricName: rubricName
};
}).map(function (res) {
// Doing more stuff with layers then call the main callback
callback(out);
});
});
But I think I'm totally going wrong, I'm thinking about flags and cie as if I was using PHP. I guess I didn't get the "node logic" programming.

Categories

Resources