Google Tools for Drawing Building Footprints
In this tutorial, we'll deep dive into some riveting uses of the refreshing drawing tools API.
- We'll learn how to dynamically generate a grid and save it as a client-side geometry.
- We'll also build a mere categorisation tool that classifies based along substance abuser-drawn points.
Note: if you are copy and pasting the code snippets downstairs into the Code Editor, they may not work without drawing off a geometry first.
Let's get started!
Background
Before the Drawing Tools API, geometries had to be rendered using Map.addLayer(geometry)
, which means they had to round trip to the server whenever the user panned Oregon zoomed on the map. With the original drawing tools API, we can right away return those geometries like a shot on the map as if we had drawn them ourselves!
Documentation on the drawing tools API is found Hera: https://developers.google.com/Earth-engine/ui_widgets#ui.map.drawingtools.
The drawing tools API adds two new components to the ui.Map
API:
-
ui.Map.DrawingTools
, and -
ui.Map.GeometryLayer
.
Unitedly, these manage the geometry redaction operations also as any of the geometry layers along the draft tools. The drawing tools provide a valuable hardened of functionality that allow you to listen for when the exploiter draws, edits, Beaver State deletes a geometry, atomic number 3 well as adding, removing, or selecting geometry layers.
To retrieve the drawing tools for a map, simply call the drawingTools()
method on the correspondenc:
var drawingTools = Map.drawingTools();
Unlike the default Map
, freshly created ui.Maps
don't get drawing tools by default, but we can add drawing tools to newly created maps by simply calling the drawingTools()
method to recover them:
var map = ui.Map(); // Prints true since drawingTools() adds drawing tools to the map. print(map.drawingTools().getShown()); // Put back the default Mapping with the freshly created map. ui.ascendant.widgets().reset([map]);
Note: you can only have incomparable drawing tools on a map out at a time.
Use the layers()
method acting on the draught tools to retrieve an ActiveList
of the geometry layers. A geometry bed contains a list of geometries that you can obtain by career the geometries()
method acting on the layer. There are two ways to add new geometry layers to the drawing tools:
var drawingTools = Map.drawingTools(); // Use the addLayer method acting on the drawing tools straightaway. var geometries = [ee.Geometry.Point([0, 0]), ee.Geometry.Rectangle([[0, 0], [1, 1]])]; drawingTools.addLayer(geometries, 'my_geometry1', 'red'); // Create a new GeometryLayer and add it to the layers list. volt-ampere layer = ui.Map.GeometryLayer(geometries, 'my_geometry2', 'blue'); drawingTools.layers().add(bed); // Print the list of geometries. print(layer.geometries()); // Add a geometry to the layer. layer.geometries().ADHD(ee.Geometry.Point(1, 1));
There are some nice contour options for the drawing off tools:
-
setShown(
boolean
)
changes the visibility of the drawing tools. -
setDrawModes(['point', 'line', 'polygon', 'rectangle'])
changes the available cast modes to the user. -
setShape('point')
sets the modern thread shape and starts draw modal value. -
setLinked(
Boolean
)
configures whether geometries are coupled to the imports.
Let's take a deeper look at setLinked
, since it's surprisingly utile!
- When linked mode is turned on, the geometries in the drawing tools are linked to the imports panel.
- When linked mood is turned cancelled, the geometries in the imports panel testament be removed from the drawing tools (they won't be deleted), and newly created geometry layers will not be added to the imports.
The default Map
has linked mode soured happening away default whereas a newly created ui.Map out
has linked way turned off aside default.
Caution: If you delete a geometry layer while linked mode is happening (like calling layers().readjust()
), they will atomic number 4 deleted from the imports!
Example: Linked Maps
Suppose you wanted to spend a penny a split mapping where geometries are reflected on both maps. All you experience to do is turn connected linked mode for some maps' drawing tools and any geometries that are worn or edited on one map will be mirrored on the other!
Here is a show script:
volt-ampere map1 = ui.Map(); map1.drawingTools().setLinked(apodeictic); var map2 = ui.Map(); map2.drawingTools().setLinked(typical); ui.Map.Linker([map1, map2]); ui.root.widgets().readjust([ui.SplitPanel({firstPanel: map1, secondPanel: map2})]); // Immediately try drawing a geometry on both maps!
Figure 1. A split map with geometries reflected on both sides.
Neat, properly? Let's do something even more advanced!
Node Side Geometries
Side by side up, we'll find out how to dynamically generate a grid in Terra firma Engine and add IT as a client-go with geometry so that it South Korean won't be rerendered every prison term the map viewport changes.
The draftsmanship tools let you take a server-side geometry and render it on the client. This requires us to use appraise
on the server-side geometries to fetch them from the waiter and spend a penny them accessible to be raddled straight along the map.
Also new, your strange geometries straight off point up in Apps so you only deliver to run your expensive geometry translation code once.
For illustration, suppose that you rendered a interlocking geometry in Earth Engine, you can add it to the map as a customer broadside geometry (with linked mode along). When you publish the app, the prerendered and foreign geometry will already be at that place; no more further coding required! Here's an example of that in action:
Example: Build a Node-Side Power system
Here's a link to the full script. Press go to see the generated power system!
Note: this example doesn't work with all projections and doesn't work well with banging geometries, so keep that in mind.
First, we'll need to compose a function that generates a grid supported on a geometry. Draw a geometry around a region of interest and then run the following script:
// pixelLonLat returns an image with each pixel labelled with longitude and // latitude values. var lonLat = ee.Image.pixelLonLat(); // Select the longitude and latitude bands, procreate by a multitude then // truncate them to integers. var lonGrid = lonLat .choose('longitude') .reproduce(10000000) .toInt(); var latGrid = lonLat .select('latitude') .manifold(10000000) .toInt(); // To make the reference grid, multiply the latitude and longitude images and then wont // reduce to vectors at the 10km resolution to group the grid into vectors. volt-ampere grid = lonGrid .multiply(latGrid) .reduceToVectors({ geometry: geometry, // This is undefined until you draw a geometry. scale: 10000, geometryType: 'polygon', }); Map.addLayer(grid);
And then we enumerate over the generated Features
in the FeatureCollection
and build a heel of LinearRings
that we'll use to anatomy our GeometryLayer
.
// Fetch the features in the grid and produce a list of linear rings. // The grid variable is defined in the step above. volt-ampere geometries = grid.toList(1000).map(function(feature) { var featureGeometry = ee.Feature article(characteristic).geometry(); var coordinates = featureGeometry.coordinates().get(0); rejoi EE.Geometry.LinearRing(coordinates); });
At last, we add together the geometries to the mapping using the drawing tools. Notice that we utilise evaluate
to fetch the geometries guest-side of meat in order to add them to the drawing tools.
var drawingTools = Map.drawingTools(); // Fetch the geometries so they potty exist added to the lottery tools. geometries.evaluate(work(geometriesList) { var layer = ui.Map.GeometryLayer({ geometries: geometriesList, name: 'grid', color: 'black', shown: true, // Show the layer (already defaults to true). secured: true, // Lock the bed. }); drawingTools.layers().set(1, layer); });
There! Directly we take in a grid that is rendered client-side that South Korean won't go away when the map viewport changes. Notice that the geometry is uneditable; that's because we constructed the stratum with the locked
property place to true
.
Figure 2. A map with a grid over the San Francisco Bay Area.
We'll refactor the code to use functions to realize it more reusable:
function makeGrid(geometry, scale) { // pixelLonLat returns an mental image with each pixel labeled with longitude and // latitude values. var lonLat = ee.Image.pixelLonLat(); // Take the longitude and parallel bands, multiply by a plurality then // truncate them to integers. var lonGrid = lonLat .select('longitude') .multiply(10000000) .toInt(); volt-ampere latGrid = lonLat .select('latitude') .multiply(10000000) .toInt(); // To produce the grid, multiply the latitude and longitude images and then use // reduce to vectors at the 10km resolution to group the grid into vectors. return lonGrid .manifold(latGrid) .reduceToVectors({ geometry: geometry, // This is undefinable until you draw a geometry. scale: descale, geometryType: 'polygon', }); } function buildGeometryList(grid, terminus ad quem) { return grid.toList(fix).map(mathematical function(feature film) { var featureGeometry = ee.Lineament(have).geometry(); var coordinates = featureGeometry.coordinates().get(0); return EE.Geometry.LinearRing(coordinates); }); } volt-ampere grid = makeGrid(geometry, 10000); // 10 km scale var geometries = buildGeometryList(power grid, 1000); volt-ampere drawingTools = Map.drawingTools(); geometries.evaluate(function(geometriesList) { volt-ampere layer = ui.Represent.GeometryLayer({ geometries: geometriesList, name: 'control grid', color: 'black', shown: true, // Show off the layer (already defaults to true). locked: true, // Lock the layer. }); drawingTools.layers().bent(1, stratum); });
Since the grid has already been added to the imports, the grid
variable is now available for US to usance, sol we john comment out the bit that generated the grid primarily to save us any computation time.
// var gridiron = makeGrid(geometry); // var geometries = buildGeometryList(grid); // volt-ampere drawingTools = Map.drawingTools(); // geometries.evaluate(function(geometriesList) { // var level = ui.Map.GeometryLayer({ // geometries: geometriesList, // advert: 'grid', // color: 'melanise', // shown: true, // Show the layer (already defaults to even). // barred: true, // Whorl the level. // }); // drawingTools.layers().set(1, stratum); // });
If you publish this as an app, since the imports behave over to Apps, the grid volition Be there, primed to go!
Upshot manipulation
Finally, the drawing tools API allows us to listen for when users interact with:
- individual geometries,
- layers,
- or the drawing tools themselves,
and lets United States run callback functions when they materialize. Here are some useful events you can listen for:
Drawing Tools Events:
-
onShapeChange(
callback
)
- A draw style is changed, for examplepoint
,line
,polygon
, orrectangle
.
Geometry Events (callback called with geometry, layer, and drawing tools):
-
onDraw(
recall
)
- a geometry is added to a layer. -
onEdit(
callback
)
- a geometry is edited. -
onErase(
recall
)
- a geometry is removed. -
onSelect(
callback
)
- a geometry is selected.
Stratum Events (recall called with layer and drawing tools):
-
onLayerAdd(
callback
)
- a layer is added. -
onLayerRemove(
recall
)
- a layer is removed. -
onLayerSelect(
callback
)
- a layer is selected. -
onLayerConfig(
callback
)
- a layer attribute (e.g. name, color) is varied.
We can use these to listen for user interactions with the drawing off tools for a responsive and personalized, Worldly concern Engine get!
Example: Classification With User-Drawn Geometries
The last example is adapting the Classifications model to Be more interactive. This example runs a machine learning classification using points defined in troika FeatureCollections
, i for urban, botany, and water. Currently, if we wanted to rerun the classification on recently points, we would sustain to course the book afterwards we edit the points. What if we wanted to rerun the classification mechanically when we edit the points?
We can refactor the categorization to bank on the points in the drawing tools, which are always kept up to date. To do this easier, we can use the getEeObject()
method on the GeometryLayer
to get the Globe Locomotive engine object associated with a GeometryLayer
(e.g. the underlying FeatureCollection
).
First, import the sport collections at the exceed of the file thusly we bear urban
, vegetation
, and water
in your imports (hover over the text at the top of the script and press convert
on the selection that says ""urban", "vegetation" and "water" pot be born-again to import records.
").
Figure 3. Classification tools object lesson with foreign feature collections.
Adjacent, get along the geometry layers associated with each of the have collections, since some edits to the geometries will be reflected happening the layer (add to the top of the file):
// Starter playscript: https://codification.earthengine.google.com/?scriptPath=Examples:Demos/Classification var drawingTools = Map.drawingTools(); // Only allow drawing points. drawingTools.setDrawModes(['point']); // Get the layers list. var layers = drawingTools.layers(); // Assuming the regularize is urban, vegetation, then urine. volt-ampere urbanLayer = layers.develop(0); volt-ampere vegetationLayer = layers.get(1); var waterLayer = layers.get(2);
Now that we have the layers, we can get the FeatureCollection
related with each layer by vocation the getEeObject()
method.
Change the following line from:
// Merge the three geometry layers into a uniform FeatureCollection. var newfc = cityfied.meld(flora).merge(water supply);
to:
// Merge the three geometry layers into a divorced FeatureCollection. volt-ampere urbanfc = urbanLayer.getEeObject(); var vegetationfc = vegetationLayer.getEeObject(); volt-ampere waterfc = waterLayer.getEeObject(); var newfc = urbanfc.merge(vegetationfc).merge(waterfc);
and run off the book to see that everything stayed the same. Next, we will attach to event listeners to the drawing tools to rerun the categorization whenever a geometry is changed.
First, we will bundle all of the classification code into a unwedded function titled classify
and make much modifications to the Map.addLayer
calls to accommodate for the use running multiple multiplication:
work classify() { // Merge the three geometry layers into a single FeatureCollection. var urbanfc = urbanLayer.getEeObject(); var vegetationfc = vegetationLayer.getEeObject(); var waterfc = waterLayer.getEeObject(); var newfc = urbanfc.merge(vegetationfc).merge(waterfc); // Use these bands for classification. var bands = ['B2', 'B3', 'B4', 'B5', 'B6', 'B7']; // The mention of the property on the points storing the social class label. var classProperty = 'landcover'; // Try out the composite to generate training data. Note that the // year label is stored in the 'landcover' property. var training = composite.prize(bands).sampleRegions({ collection: newfc, properties: [classProperty], scale: 30 }); // Train a CART classifier. var classifier = ee.Classifier.smileCart().train({ features: breeding, classProperty: classProperty, }); // Print some info about the classifier (specific to CART). print('Pushcart, explained', classifier.explain()); // Classify the complex. var classified = composite.classify(classifier); var pallet = {min: 0, liquid ecstasy: 2, pallet: ['red ink', 'naive', 'strict']}; var layer = ui.Map.Layer(classified, palette, 'confidential'); Map.layers().set(0, layer); // Optionally, do some accuracy assessment. Clenched fist, tally a column of // random uniforms to the training dataset. volt-ampere withRandom = training.randomColumn('random'); // We want to reserve some of the information for examination, // to head off overfitting the model. var split = 0.7; // Roughly 70% training, 30% testing. var trainingPartition = withRandom.filter(electrical engineering.Strain.lt('stochastic', split)); var testingPartition = withRandom.filter(ee.Filter.gte('random', split)); // Trained with 70% of our data. var trainedClassifier = ee.Classifier.smileRandomForest(5).take({ features: trainingPartition, classProperty: classProperty, inputProperties: bands }); // Classify the exam FeatureCollection. var test = testingPartition.relegate(trainedClassifier); // Publish the disarray matrix. var confusionMatrix = test.errorMatrix(classProperty, 'classification'); mark('Confusion Intercellular substance', confusionMatrix); } classify();
Instantly, when we call separate
, the script will function the cutting-edge interlingual rendition of the imports to run the classifier, which means we don't need to rerun the script to capture new points!
We add issue listeners to detect when the user has made any edits to the geometries and run the class
occasion. We also use ui.util.debounce
to only run it formerly all 100 milliseconds to foreclose it from firing too flying, like when the user drags a period around.
// Use debounce to call the function at the most every 100 milliseconds. drawingTools.onEdit(ui.util.debounce(classify, 100)); drawingTools.onDraw(ui.util.debounce(classify, 100)); drawingTools.onErase(ui.util.debounce(classify, 100)); Map out.centerObject(city-bred);
That's totally there is to it! Here's a tie to the full script.
Conclusion
The drawing tools API provides a rich set of features that give you full ascendency of the client-side geometries in your Earth Locomotive engine scripts and apps. We're reactive to meet what you build with them!
Fillip
Since you made it this far down, here's an Earth Locomotive engine script that recreates the drawing tools from scratch :)
Google Tools for Drawing Building Footprints
Source: https://developers.google.com/earth-engine/tutorials/community/drawing-tools
Post a Comment for "Google Tools for Drawing Building Footprints"