Giter Site home page Giter Site logo

jameslmilner / terra-draw Goto Github PK

View Code? Open in Web Editor NEW
367.0 8.0 39.0 7.79 MB

A library for drawing on maps that supports Mapbox, MapLibre, Google Maps, OpenLayers and Leaflet out the box

Home Page: https://terradraw.io

License: MIT License

HTML 0.60% TypeScript 99.07% JavaScript 0.19% Shell 0.11% Dockerfile 0.03%
draw geojson google-maps-api leaflet map mapbox-gl maps drawing maplibre polygon

terra-draw's Introduction

Terra Draw Logo

Terra Draw CI Badge npm version

Frictionless map drawing across mapping libraries.

Terra Draw centralizes map drawing logic and provides a host of out-of-the-box drawing modes that work across different JavaScript mapping libraries. It also allows you to bring your own modes!

An example of drawing geodesic lines using Terra Draw with Leaflet

Library Support

Terra Draw uses the concept of 'adapters' to allow it to work with a host of different mapping libraries. Currently supported are:

Getting Started

Please see the the getting started guide - this provides a host of information on how to get up and running with Terra Draw.

Development & Contributing

Project Website

You can check out the official Terra Draw website at terradraw.io. If you are interested in contributing to the website please see this repository.

Contact

Email: [email protected]

License

MIT

terra-draw's People

Contributors

anderswi avatar andrewadev avatar andrewbrey avatar itoxiq avatar jaiakash avatar jameslmilner avatar morehawes avatar sidling1 avatar tugark avatar uncatchablealex avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

terra-draw's Issues

Bugs

When removing Terra Draw in mapbox/maplibre

Object keys should be used here since we are using an array.

Object.keys(["point", "linestring", "polygon"]).forEach((geometryKey) => {
const id = `td-${geometryKey.toLowerCase()}`;
this._map.removeLayer(id);
// Special case for polygons as it has another id for the outline
// that we need to make sure we remove
if (geometryKey === "polygonId") {
this._map.removeLayer(id + "-outline");
}
this._map.removeSource(id);
});

Line 35 could be...
["point", "linestring", "polygon"].forEach((geometryKey) => {
and 41
if (geometryKey === "polygon") {

Style property assignment

2 things here, first the assignment is not being done correctly here

styles.pointOutlineWidth = this.styles.midPointOutlineWidth || 2;

Could be...
styles.pointOutlineWidth = this.styles.selectionPointOutlineWidth || 2;

And second, what if the user wants to set the midpoint outline width to 0, then this type of evaluation would interpret that the user hast set anything, a workaround to this right now is to set the value as '0' instead of just 0 so that JavaScript interprets its a string.

Map draggability when in select mode

Currently if you have something selected you CAN NOT move the underlying map. Basically this is because setDraggability is set to false when onDragStart is being called. I think it is better to set draggability to false only when is strictly necessary (we know for sure the user is about to perform some modification to a feature).

To achieve that we can remove this

setMapDraggability(false);

And we would need to pass in some way the setDraggability function to each of the behaviors

  • drag-coordinate
  • drag-feature
  • rotate-feature
  • scale-feature

All of them have an event as a parameter so maybe we could add it to this interface

terra-draw/src/common.ts

Lines 28 to 35 in 65ba076

export interface TerraDrawMouseEvent {
lng: number;
lat: number;
containerX: number;
containerY: number;
button: "neither" | "left" | "middle" | "right";
heldKeys: string[];
}

like setDraggability?: (enabled: boolean) => void;
which would be filled in this method of the base adapter

protected getDrawEventFromEvent(
event: PointerEvent | MouseEvent
): TerraDrawMouseEvent | null {
const latLng = this.getLngLatFromEvent(event);
if (!latLng) {
return null;
}
const { lng, lat } = latLng;
const { containerX, containerY } = this.getContainerXYPosition(event);
const button = this.getButton(event);
const heldKeys = Array.from(this._heldKeys);
return {
lng: limitPrecision(lng, this._coordinatePrecision),
lat: limitPrecision(lat, this._coordinatePrecision),
containerX,
containerY,
button,
heldKeys,
};
}

like const setDraggability = this.setDraggability.bind(this);

Then on each of the behaviors we would add this code

                if (event.setDraggability) {
			event.setDraggability(false);
		}

Between the lines 53 and 55

// If the cursor is not over the selected
// feature then we don't want to drag
if (!clickedFeature || clickedFeature.id !== selectedId) {
return;
}
const geometry = this.store.getGeometryCopy(selectedId);

Between the lines 66 and 68

// No coordinate was within the pointer distance
if (closestCoordinate.index === -1) {
return false;
}
// Store the updated coord
const updatedCoordinate = [event.lng, event.lat];

Between the lines 34 and 36

// Update the geometry of the dragged feature
if (geometry.type !== "Polygon" && geometry.type !== "LineString") {
return;
}
const mouseCoord = [event.lng, event.lat];

Again between the lines 34 and 36

// Update the geometry of the dragged feature
if (geometry.type !== "Polygon" && geometry.type !== "LineString") {
return;
}
const mouseCoord = [event.lng, event.lat];

This way the user could move the map freely even when has something selected, and only would prevent so if the user actually wants to do something with the feature.

Suggested useful methods.

getFeatureAtMouseEvent

A method to know if the mouse is over a feature and get it if so.

Currently this method could be easily implemented in the select mode like.

        getFeatureAtMouseEvent(event: TerraDrawMouseEvent){
		return this.featuresAtMouseEvent.find(
			event,
			this.selected.length > 0
		).clickedFeature;
	}

But I think that for the user of terra draw it would be better to have this implementation in the root terra draw class.
https://github.com/JamesLMilner/terra-draw/blob/main/src/terra-draw.ts

And also not pass a TerraDrawMouseEvent but a PointerEvent | MouseEvent, (and internally using getDrawEventFromEvent of the common adapter to make the conversion to TerraDrawMouseEvent ).

reRender method

A method to get a snapshot, clear everything, and add those features again. Useful if for example in the parent map we updated the whole style and get rid of the sources and layers that terra draw created, this way we could call this method and everything would be restored.

This method could be implemented in the root/main class of terra draw
https://github.com/JamesLMilner/terra-draw/blob/main/src/terra-draw.ts

	public reRender() {
		const currentMode = this._modes[this._mode.mode];

		// 1- stop current mode to remove midpoints, vertexes or unwanted features
		if (currentMode){
			currentMode.stop();
		}

		// 2- now we get only the wanted features
		const snapshot = this.getSnapshot();

		// 3- basically remove everything, store, sources and layers
		this.clear();

		// 4- apply stored snapshot
		this.addFeatures(snapshot);

		// 5- start once again the same mode the user was in
		if (currentMode){
			currentMode.start();
		}
	}

This method would imply that we can no longer assume all layers and sources are there, so for example in mapbox gl adapter

Object.keys(["point", "linestring", "polygon"]).forEach((geometryKey) => {
const id = `td-${geometryKey.toLowerCase()}`;
this._map.removeLayer(id);
// Special case for polygons as it has another id for the outline
// that we need to make sure we remove
if (geometryKey === "polygonId") {
this._map.removeLayer(id + "-outline");
}
this._map.removeSource(id);
});

We would need ensure each source/layer is there before remove/modify it, like
if (this._map.getLayer(id)) this._map.removeLayer(id);

The places we would need to add this validation are:

this._map.removeLayer(id);

this._map.removeLayer(id + "-outline");

this._map.removeSource(id);

(this._map.getSource(id) as any).setData({
type: "FeatureCollection",
features: features,
});

pointId && this._map.moveLayer(pointId);

Drawing Different Colored Polygons

I am using MapLibre and TerraDrawMapLibreGLAdapter. I want to draw many polygons on my map and have each be a different color. I have already tried a few things:

1.) Defining multiple modes with distinct TerraDrawPolygonMode objects (each with a unique styling) in the constructor for my TerraDraw object, and then switching modes before drawing each polygon with setMode. This didn't work. Any mode not keyed with "polygon" will not allow the user to draw anything. This behavior is strange because in the docs the constructor definition for TerraDraw is given as:
new TerraDraw(options: { adapter: TerraDrawAdapter; modes: { [mode: string]: TerraDrawBaseDrawMode<any>; }; }): TerraDraw
which seems to imply that the key for each mode can be any string (when in reality, in order to draw a polygon, the key must be the string literal "polygon").

2.) Using a single polygon mode and calling setModeStyles on my TerraDraw object with a new styling before drawing the next polygon. This causes every polygon to change color. I guess this makes sense, but it's not what I'm after.

3.) Using separate TerraDraw objects, each with a polygon mode with unique styling and switching between them. Using one TerraDraw object to draw seems to erase all previously drawn polygons from other TerraDraw objects.

I also tried playing around with the render() method on my TerraDrawMapLibreGLAdapter. I didn't get anywhere with that either. I'm new to Terra Draw and I'm not sure where to go from here. Any suggestions would be appreciated.

Thank you, guys.

Performance Improvement on Mapbox/Maplibre adapters.

I have noticed that I get a lot of lag while drawing with terra draw on maplibre (mapbox really since its the adapter that does everything).

For some reason it gets notoriously worse when you have the browser console open.

First I came to that conclusion since in the demo https://terradraw.io/ (it uses leaflet) it goes perfectly.

I was able to improve the performance notoriously locally and I'm going to explain all the process.

The idea is to reduce the most changes as possible to the maplibre sources and layers when handling the pointermove event (which is the most frequently fired)

I will start from the most impacting things

Updating closing points

Is it really necessary to update the closing points every time the user moves the pointer?, I think this line could be deleted without impacting anywhere. And it improves the fluency of the movements a lot.

if (this.closingPoints.ids.length) {
this.closingPoints.update(updatedCoordinates);
}

This way instead of calling the _render() method of the adapter 2 times per movement now it should only be called once.

Only updating source if necessary

Currently all the sources are updated even if there wasn't any change on them.

const pointId = this._setGeoJSONLayerData<Point>(
mode,
"Point",
points as Feature<Point>[]
);
this._setGeoJSONLayerData<LineString>(
mode,
"LineString",
linestrings as Feature<LineString>[]
);
this._setGeoJSONLayerData<Polygon>(
mode,
"Polygon",
polygons as Feature<Polygon>[]
);

Why don't add a validation for each like

if(points.length || areDeleted)
if(linestrings.length || areDeleted)
if(polygons.length || areDeleted)

And of course define the areDeleted const at line 366, so that we would correctly delete features when needed.
const areDeleted = changes.deletedIds.length;

If we do this we should divide the features in changed and unchanged so that we ONLY update a source when there are changed features, and not every time we have unchanged (which is usual when user have draw different features already).

Creating a lot of sources and layers

Right now for each mode terra draw creates 3 sources and 4 layers, so potentially if a user has all modes enabled there could be 30 sources and 40 layers being updated on each movement.

There could be only 3 sources and 4 layers no matter the amount of modes.

Again, the most we can reduce the amount of sources and layers to update per movement the better.

I guess it was first created that way so that each mode would have its own layer potentially with its own different style.
But if that's true then each feature style is being described in its properties.

properties.pointColor = styles.pointColor;
properties.pointOutlineColor = styles.pointOutlineColor;
properties.pointOutlineWidth = styles.pointOutlineWidth;
properties.pointWidth = styles.pointWidth;

And then being styled based on those properties using maplibre expressions
"fill-color": ["get", "polygonFillColor"],
"fill-opacity": ["get", "polygonFillOpacity"],

So it seems like there is not really a need of having separate sources and layers per mode.

We could also delete the filters of all the layers

filter: [
"all",
["match", ["geometry-type"], "Polygon", true, false],
["match", ["get", "mode"], mode, true, false],
],

Option to use maplibre outline instead of creating other layer

So far applying the above improvements the performance get much better but even having to update 2 layers it seems to cause a little bit of lag in maplibre, so the proposal is to have an option in maplibre adapter to avoid running this line.

this._addFillOutlineLayer(id, mode, beneath);

And instead use the outline of maplibre (which I understand is not highly customizable, but at least wont impact as much in the performance).

https://maplibre.org/maplibre-style-spec/layers/#paint-fill-fill-outline-color

And apply it here:

"fill-color": ["get", "polygonFillColor"],
"fill-opacity": ["get", "polygonFillOpacity"],

like: "fill-outline-color": ["get", "polygonOutlineColor"]

This way people could chose between the best performance or slightly worse but with better outlines.

Results

Doing all this we dramatically reduce the amount of work have to be done by maplibre and the fluency of the movements.

Description Pointer Movement Render Calls Sources Updated Layers Impacted
All modes enabled, current behavior 1 2 30 40
All modes enabled, fixed with outline in another layer 1 1 1 2
All modes enabled, fixed without outline 1 1 1 1

Sending a PR for you to test it, and maybe use it to apply the changes by your own. Thanks for the help ❤️

Configurable Cursor Styling

This was originally raised in #38 but raising as a separate issue to more easily track it.

The purposed of this issue is to explore how viable it would be to support configurable cursor styling, so users can control what cursors appear on given events.

Support globes

At the moment drawing on a globe seems to be mostly supported having tested with MapboxGL JS globe projection.

The one thing that seems to be broken is midpoints as they appear slightly off. It may be that we simply need to implement a rhumb line midpoint which can be toggled with a parameter to polygon mode.

There may be other things that don't function quite as expected on globes so I'll keep this issue open as a catch all for now.

Excalidraw library integration

Hello watched your FOSS4G talk, thanks for sharing this project.
Currently working on a project using mapbox and ordinance survey data for collection geotagged photos in the field. (see protype https://buildingshistory.co.uk/#18.25/51.367998/-2.202375/0/37
Currently you can import/export geojson features buildings, was wondering if you have considered integrating terra-draw with Excalidraw library? https://github.com/excalidraw/excalidraw
My use case would be able to add/edit polygons in browser but have version control via excalidraw.

Freehand LineString mode

Just creating this ticket so it doesn't get lost - @kuanb suggested a Freehand LineString tool similar to the Freehand Polygon mode that is available. This would be reasonably straight forward to implement - there question of if it should also be a 'core mode' or handled externally.

UX feedback

LineString

How do you stop drawing? I've tried a few keys without effect... Escape cancels completely. mapbox-gl-draw uses enter to finish.

If I click while moving my mouse quickly, there's no effect -- I'm guessing there's a threshold param and it's counting as a drag. All of the sudden direction changes are a click, but only ones where I stopped long enough seem to count:

screencast.mp4

Freehand

Feels choppy, but guessing this is known. :)

Initial line of a Polygon Mapbox and MapLibre disappears at 0 and 180 degrees whilst drawing

Whilst drawing a using the Mapbox or MapLibre adapters the initial line appears to disappear when in the 0 and 180 degree positions. See this GIF for an example:

flashing-mapbox.mp4

This should be fairly easy to replicate, simply start drawing a polygon using the Mapbox Adapter and rotate the line 360 to see it flash and reappear as you move past the 0 and 180 degree marks.

MapLibre Adapter

Creating a specific adaptor for MapLibre will give better type safety for MapLibre and also allow any future drift in APIs to be accounted for. It is possible that some of the logic could be reused via shared functions or using the MapLibre adapter internally and just wrapping around it for now.

Unclear user experience on how to close a LineString

Original issue for this is #2 (thank you @dabreegster!) , I am extracting this so it can be tackled in isolation.

Currently it's not super clear how to close a LineString. I think it would make sense to take a similar approach to Polygons where we create a visible closing Point which has a pointer cursor on hover. This hopefully makes it more obvious to the user that the way to close the geometry is by clicking on it.

For reference here is the current behaviour:

linestring-unclear.mp4

Allow closing of geometries with a keybinding

Original issue for this is #2 (thank you @dabreegster!) , I am extracting this so it can be tackled in isolation.

Being able to close a geometry using a keyboard shortcut whilst drawing may be a nice improvement and probably wouldn't be too much work to implement. This approach is taken in other open source drawing libraries, so people may expect this behavior.

I would recommend that as with all keybindings in Terra Draw, we make it configureable.

Ensure all adapters are unregistered correctly

I haven't fully checked, but my gut instinct is that the unregister methods for the adapters maybe be out of date/sync with the register methods. We should ensure these are updated to reflect the state of the register methods.

Essentially we need to:

  1. Go through each adapter
  2. Look at the registered events
  3. Ensure there is an equivalent unregister event in the unregister method

Base Adapter "getDrawEventFromEvent" BUGS

First of all thanks for sharing the project. ❤️

Bug 1 - Offset Calculation

Current State:

If the parent element of the mapContainer has an offset, the cursor position is not calculated correctly.

Problem:

Currently the map container is being goten.

const container = this.getMapContainer();

Then its offsetLeft and offsetTop properties are used to calculate containerX and containerY.
containerX: event.clientX - container.offsetLeft,

This properties get the offset from the parent only, but dont take into account the total offset if the map container is highly nested in the DOM.
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetLeft

Proposal:

Use the same approach of the mapbox-gl adapter on its method getLngLatFromEvent

const { left, top } = this.getMapContainer().getBoundingClientRect();

Other approach could be to use the "project" method since latlng is already calculated at that point.

Bug 2 - Spreading heldKeys

Current State:

You cant use the rotate and scale functionality, not even in the official demo: https://terradraw.io/
I tested it on Chrome and Edge

Problem:

Spreading the Set, it seems to be a problem with Typescript.

heldKeys: [...this._heldKeys],

Proposal:

Convert the set to array and then spread it.

Better handling of map draggability in Select mode

See the last heading of #37 for further details. Essentially we could be a little bit smarter about when we choose to disable dragging. At the moment we just blanket block draggability when a geometry is selected. We could look into seeing if it is actually possible to see if we could make this only happens when we try to perform specific drag based actions (drag, drag coordinate, rotate, scale etc).

Add way to programmatically add features to drawing instance

Having a top level API to add features to a Terra Draw instance would be valuable and has been raised by @bdon separately. Other drawing libraries have this feature. There is already a data property that can be used to load data at instantiation but actually I believe this would be better superseded by a addFeatures method at on the Terra Draw instance and the data property becomes deprecated. The complex part would be validating features, but I think we can do this by adding a method called validateFeature to all modes that ensures the mode geometry is valid.

Keyboard events are swallowed for elements nested in map

This was raised in #29 but I think it makes sense to split it out into it's own issue. This appears to be a problem when blanket calling event.preventDefault() stops native inputs (and likely other elements) from behaving as expected when they are nested within the map element.

One solution to this problem could be to only prevent default when we are certain that the keys pressed are for some other action (rotation, scaling etc).

Reproduction case:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="initial-scale=1,maximum-scale=1,user-scalable=no"
    />
    <script src="https://unpkg.com/[email protected]/dist/maplibre-gl.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/terra-draw.umd.js"></script>
    <link
      href="https://unpkg.com/[email protected]/dist/maplibre-gl.css"
      rel="stylesheet"
    />
    <style>
      body {
        margin: 0;
        padding: 0;
      }
      #map {
        position: absolute;
        top: 0;
        bottom: 0;
        width: 100%;
      }
      #otherInput {
        z-index: 100000000000000;
        position: absolute;
        margin: 10px;
      }
    </style>
  </head>
  <body>
    <div id="map"></div>
    <script>
      var map = new maplibregl.Map({
        container: "map",
        style: "https://demotiles.maplibre.org/style.json",
      });

      const otherInput = document.createElement("input");
      otherInput.id = "otherInput";
      document.body.appendChild(otherInput);

      class HelloWorldControl {
        onAdd(map) {
          this._map = map;
          this._container = document.createElement("input");
          this._container.className = "maplibregl-ctrl";
          return this._container;
        }

        onRemove() {
          this._container.parentNode.removeChild(this._container);
          this._map = undefined;
        }
      }

      map.on("load", () => {
        const td = new terraDraw.TerraDraw({
          adapter: new terraDraw.TerraDrawMapLibreGLAdapter({
            map: map,
            coordinatePrecision: 9,
          }),
          modes: {
            rectangle: new terraDraw.TerraDrawRectangleMode(),
          },
        });
        td.start();
        td.setMode("rectangle");
      });
      map.addControl(new HelloWorldControl());
    </script>
  </body>
</html>

Auto-generate docs via TSDocs

To make sure people actually know how to use the API of the library, we want to use TSDoc comments and then auto generate docs for these with something like TypeDoc.

Performance of Select Mode for MapboxGL/MaplibreGL Adapters

There have been reports (#44) that selection mode in MapLibreGL Adapter could be better.

I don't have any intimidate ideas of how to improve this as most of the performance improvement options were exhausted by #30. One option for users is to increase the dragEventThrottle up from 5 to 10/15. This may help slightly in rendering performance however it may change the UX of dragging for users (worth just experimenting and seeing what feels right).

Select mode has issues dragging features across poles/anti-meridians

Originally raised in #44

Currently dragging features causes visual issues when dragging geometries over the anti-meridian and poles. There is a probably a deeper solution to this problem, but for now it may just make sense to prevent this from happening due to the render artefacts and potentially corrupt data.

Add method to remove features programmatically

As we are looking at adding a method for adding features to a Terra Draw instance, it makes logical sense to also allow a method for removing specific features programmatically. Obvious use cases are if someone wants to remove features based on some UI operation rather than via interacting with the map.

Make built in draw modes keybindings optional

In the name of configurability, it probably makes sense to not enforce key bindings in the builtin draw modes. This means if library users do not want their users to cancel and finish a geometry using keyboard shortcuts they can disable them.

Freehand mode drawing experience could be smoother

Original issue for this is #2 (thank you again @dabreegster!) , I am extracting this so it can be tackled in isolation.

The problem here is that the Freehand drawing tool currently uses a rate limiting/throttling mechanism against the incoming events, to prevent too many points being created and hence a large amount of data. However, as pointed out this can lead to a bit of a choppy experience whilst drawing. The solutions here are to potentially decrease the aggressiveness of the throttling via the reducing the everyNthMouseEvent default property, or to take a different approach such as a minimum distance threshold (probably best to implement in pixel distance). The pixel distance approach would probably be the smoother of the two approaches but it would be good to experiment. There may also be other approaches, or a combination which result in better results with regards to the smoothness/data size tradeoff.

Personalization

Dashed Lines / Outlines

It would be nice to add a way to set the outlines of the features as dashed lines with a custom array like
https://maplibre.org/maplibre-style-spec/layers/#paint-line-line-dasharray

Cursor style

An option to let the user choose which cursor style wants. This because I think is better to have the move style
image
when hovering a selected future in select mode. Also, to display this style ONLY when hovering the selected feature.

Dragging selection points outside map

Hi @JamesLMilner I have a set of issues I will post individually, hope you are doing well.

Currently there's a validation of the features position to keep them inside the lat and lng range. This is being done when you drag the whole feature, but it is possible to drag a single vertex of a feature (selection point) outside this boundaries.

Some suggestions from free hand drawing tools

Hi, I'm not a programmer, just someone that would like to use mapbox to teach geography and history live, using the program as a blackboard. For this, the plan is to have access to cool set of drawing tools. This is why i feel very proud and hopeful of this kind of deep technical work you're undergoing!

Ideally, as a feedback from a layman, i would hope to have a few types of freehand drawing:

  • Marker style, with opacity and thickness options, in which strokes applied over each other make the color more opaque (like https://www.scribblemaps.com/create does it)
  • Highlighter style, with opacity and thickness options, in which a stroke overriding an earlier one doesn't tint the area more (so you don't ruin the coloring of an area just because you left a small gap undrawn )
  • A fill in button with intensity parameter, like in traditional drawing programs.
  • The already present freehand polygon is also cool! Maybe an option of modifying the shape of its vertices after the polygon is placed would be nice too, in maybe the color andthickness of the border too.
  • Advanced tool: LAMPS. Is it possible to tinker with the atmospheric light of programs such as mapbox, so as that one can place light sources, like in a videogame engine like unity? I have no idea but i still write the idea here just in case.

Thank you for listening!!

Edu from Barcelona

Bugs and Suggestions

Hi its me again hope you are doing great, I have a new report. Thanks.

Moving too fast in select mode

If you abruptly drag a feature, you can get out of it.
And if you move close to a vertex, you can move it.
All of this without releasing the left mouse button.

dragFast

This happens because terradraw validate if the mouse is inside the figure each time we drag before moving it, we could change that validation to do it only when dragging starts, not every time 'drag' event is fired. And once we know the user started with a valid position we can move it wherever the event is saying.

Proposed Solution:
Preserve the user choice to drag the whole feature or just a vertex when start dragging (onDragStart), and just keep updating that option (onDrag), this way no matter how fast the user moves, as long as it does not release the button terradraw will only update the initial choice.

Check if the user wants to drag a vertex or the whole feature
https://github.com/JamesLMilner/terra-draw/compare/main...alanOracle:terra-draw:studio-approach?expand=1#diff-66b8b33e810bc4e27d8af887cbf5f6415b2ad6156d33ed947972cb459ca94e3bR475-R497

Then on drag validate which type of drag we are doing and ONLY do that one.
https://github.com/JamesLMilner/terra-draw/compare/main...alanOracle:terra-draw:studio-approach?expand=1#diff-66b8b33e810bc4e27d8af887cbf5f6415b2ad6156d33ed947972cb459ca94e3bR586

Mapbox/Maplibre Performance in select mode

Although now terra draw has a great performance in maplibre/mapbox there is still a lot of lag when in select mode.
This is likely because it has to update 3 layers (polygon, outline and points) each time we drag anything.

So first of all I know that terra draw wants to keep the same design and style across all adapters so maybe this is not the best solution.

But here it is, basically an approach to this could be to avoid updating the points layer when on select mode.

This could be done by checking if the user want to drag the whole feature, then onDragStart delete all selection and mid points, and restore them onDragEnd so that when the user drags would only see the polygon moving without any points.

In the other hand if the user want to drag only a vertex then delete all midpoints (because I discovered that all of them are sent to render method no matter if they are actually updated or not) and also delete the single selection point that the user selected, this way we wont update the points layer since the remaining selection points are not moving anywhere and could serve as reference.

Delete selection and midpoints onDragStart
https://github.com/JamesLMilner/terra-draw/compare/main...alanOracle:terra-draw:studio-approach?expand=1#diff-66b8b33e810bc4e27d8af887cbf5f6415b2ad6156d33ed947972cb459ca94e3bR499-R533

Re-construct points onDragEnd
https://github.com/JamesLMilner/terra-draw/compare/main...alanOracle:terra-draw:studio-approach?expand=1#diff-66b8b33e810bc4e27d8af887cbf5f6415b2ad6156d33ed947972cb459ca94e3bR621-R624

Features are not updated when there's a change in style

This happens in maplibre/mapbox adapter since style changes passes an array of all features as unchanged to render method.
Which now for performance reasons wont do anything if everything is unchanged.

Proposed Solution:
Just as we have a deletionOccured in render mode of, we could also have somthing like
const styleChangeOccured = Object.values(this.changedIds).every(change=>!change) && changes.unchanged.length > 0;
To force the update of the features if so.

For example
https://github.com/JamesLMilner/terra-draw/compare/main...alanOracle:terra-draw:studio-approach?expand=1#diff-b0a667c9d571e4bfe0433d3e4fb6fbe181bec821b260b5250e1b6dffd073de99R366-R373

Error when quitting terradraw while have something selected

To reproduce it:
Using maplibre adpater, select a feature, then call the stop method of terra draw (which deletes everything).

You will see all features being deleted for a fraction of second and then the selected feature will appear again but without any points and not being able to modify it.

This happens because when we call the stop method of the main terradraw class then it correctly call the cleanUp method of the mode which will delete all selection and midpoints of the figure, but because the render method is waiting 16 milliseconds to do the update and as it waits we delete all features then at the end we would see only the selected feature again on the map.

Proposed Solution:
One way it can be solved its just by removing the debounce of the render method of the mapbox adapter

Removing debounce rendering
https://github.com/JamesLMilner/terra-draw/compare/main...alanOracle:terra-draw:studio-approach?expand=1#diff-b0a667c9d571e4bfe0433d3e4fb6fbe181bec821b260b5250e1b6dffd073de99L295-L303

Avoid error when moving figure to the edges of the map

Currently there's an error when you move a figure outside the current map in maplibre adapter (when you have setRenderWorldCopies ON).

Also it would be nice to prevent figures going outside the latitude range (-90,90).

For example validating -85 , 85 in lat
https://github.com/JamesLMilner/terra-draw/compare/main...alanOracle:terra-draw:studio-approach?expand=1#diff-ace904c8e4070082cedc13adb31cfeb6a24851b1aea7ac5bc6304a6787903b27R68-R86

Prevent right click

It would be nice to have an option to ignore all right clicks so that it doesn't mix with other actions to be performed outside terradraw

Automated change log

It would be beneficial to have an automated change log that can be used to generate a human friendly change log for library users.

Coordinate being dragged can be switched whilst dragging

The coordinate that is selected to be dragged can switch whilst dragging if two coordinates are close together. This is because we check to see the closest coordinate to the cursor on every drag event. We probably need to store the coordinate index and ensure only that coordinate is updated on drag event.

Programmatic way to exit drawing mode when done drawing

What's the right way for me to end a drawing mode? Example use case: The user can either draw a rectangle or a polygon; when they have finished dragging the rectangle or closed the polygon, the mode should stop and the cursor should change back to the normal browsing/panning one (The library seems to swallow keyboard events while a mode is active too).

Thanks!

Improve mobile/tablet drawing experience

Currently when I am using the drawing experience on my mobile I am finding it hard to draw anything other than points, even though the drawing experience in the Chrome/Edge device emulate works reasonably well. Specifically drawing Polygons/LineStrings seems to not draw the initial point without significant effort/retries. It would be good to investigate what is going on here!

Unit test adapters

At the moment the adapters are not currently covered in the unit tests. It would be good to get coverage on these in particular as they are publicly exposed.

Snapping to roads

Hi! I just watched your FOSS4G talk and wow, releasing this library couldn't have come at a better time for me. I've been working on a tool to quickly sketch line-strings that snap to some kind of network. Right now my approach hacks on top of mapbox-gl-draw, and every time I peek into its codebase to figure out how a proper integration would work, I'm daunted by the complexity (and not inspired by the backlog of PRs the repo is accumulating). I'd love to explore building on top of terra-draw instead (and reap the benefits of Leaflet support too!)

You can try the tool so far here. After it loads, click the button on the right. If you're curious about the implementation, see https://github.com/acteng/atip/tree/main/route-snapper. I'm new to modern web dev (and sticking to vanilla JS so far). Most of the logic is happening in a Rust layer right now, compiled to WASM. That's mostly due to convenience for me (it's a very familiar language) -- the code is simple enough to write in JS or TS for sure, and the speed is probably fine. The routing itself works off a static network loaded once from a file, so it's bound to a fixed area. When that area gets large enough, contraction hierarchies or a similar graph preprocessing technique might be worth trying instead of A*. And in that case, I'd lean heavily towards just building something like https://github.com/easbar/fast_paths/ to WASM.

I have more ideas about snapping based on arbitrary data preprocessed from OSM, like drawing areas that can snap to the edge of roads or waterways. There's a whole space of techniques for getting the idea to work outside of a fixed area too... I'll probably be exploring tiling and dynamically generating the networks soon. (The latter idea would be something like an Overpass query to extract OSM data wherever the viewport is, then piping through a Rust library to turn into the right kind of network).

Anyway, I'll keep a close eye on this repo as it evolves. From a quick skim, I'd be way more comfortable making a proper integration with this than mapbox-gl-draw. Let me know if you have any plans around snapping that might be relevant!

Method to correctly remove terra draw

Hi its me again, I hope you're doing well, now with suggestions, they are not really bugs since you can workaround them the way I will explain in the following...
Thanks again for sharing the project ❤️

Update clear() method to correctly delete all features

Currently when you use the clear() method it just makes the store property to point to a new empty object.

this.store = {};

But it seems that the previous store is being referenced someplace else since when you call this method it does not remove the features.

CUERRENT WORKAROUND:

Use the delete() method of the store, passing it all the stored ids.
terraDrawInstance._store.delete( Object.keys(terraDrawInstance._store.store) );

Method to delete sources and layers

Currently when using mapboxgl adapter some sources and layers are created by terra draw, with a certain id pattern.

private _addGeoJSONLayer<T extends GeoJSONStoreGeometries>(

It would be nice to have a method to remove those sources and layers when the user is done using terra draw.

CUERRENT WORKAROUND:

Deleting the sources and layers manually by...

Get the modes of the terra draw instance
const modes = Object.keys(terraDrawInstance._modes);

Iterate over them removing first layers and then source for each of the feature types (point, linestring, polygon).

These are the sources and layers that are created for each mode:

layer td-${mode}-point
source td-${mode}-point

layer td-${mode}-linestring
source td-${mode}-linestring

layer td-${mode}-polygon
layer td-${mode}-polygonoutline
source td-${mode}-polygon

Dashed Lines and Outline Styling

This was originally raised in #38 but raising as a separate issue to more easily track it.

The purposed of this issue is to explore how viable it would be to support dash lines and outlines across mapping libraries. There is a somewhat philosophical question of if we we provide a fall back to a normal line if it is not supported and if we should have 'best effort' features in Terra Draw to allow support for more complex options.

Great Circle drawing mode

The great circle distance is the shortest distance between two points on the surface of a sphere, such as the Earth. It's the length of the path along a great circle line that connects the two points.

It would be cool to be able to implement a great circle line drawing tool into Terra Draw as one of the built in modes.

Here's a demonstration of the Great Circle distance:

image

Credit: Wikipedia

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.