Skip to content

Commit

Permalink
Drafting an actual library feature around mapbox-gl-draw
Browse files Browse the repository at this point in the history
Closes #2
  • Loading branch information
mstahv committed Feb 20, 2024
1 parent e997bbf commit 3c4ad0f
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 9 deletions.
150 changes: 150 additions & 0 deletions src/main/java/org/vaadin/addons/maplibre/DrawControl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package org.vaadin.addons.maplibre;

import com.vaadin.flow.component.page.PendingJavaScriptResult;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.geojson.GeoJsonReader;
import org.parttio.vaadinjsloader.JSLoader;

import java.util.ArrayList;
import java.util.Collections;
import java.util.EventObject;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;

/**
* A "raw" Java API for mapbox-gl-draw control, that is
* compatible with maplibre. Actual fields can then utilize
* this API.
*/
public class DrawControl {

public class ModeChangeEvent extends EventObject{
private final DrawMode drawMode;

public ModeChangeEvent(DrawMode drawMode) {
super(DrawControl.this);
this.drawMode = drawMode;
}

public DrawMode getDrawMode() {
return drawMode;
}
}

@FunctionalInterface
public interface DrawEventListener<T extends EventObject> {
public void onEvent(T event);
}

private List<DrawEventListener<ModeChangeEvent>> modeChangeListeners;
public void addModeChangeListener(DrawEventListener<ModeChangeEvent> listener) {
if(modeChangeListeners == null) {
modeChangeListeners = new ArrayList<>();
map.getElement().addEventListener("modechange", e-> {
ModeChangeEvent event = new ModeChangeEvent(DrawMode.valueOf(e.getEventData().getString("event.mode").toUpperCase()));
modeChangeListeners.forEach(l -> l.onEvent(event));
}).addEventData("event.mode");
}
modeChangeListeners.add(listener);
}

public enum DrawMode {
SIMPLE_SELECT,
DIRECT_SELECT,
DRAW_LINE_STRING,
DRAW_POLYGON,
DRAW_POINT
}

private final MapLibre map;
private final String id = "draw" + UUID.randomUUID().toString().substring(0,4);

private DrawMode mode = DrawMode.SIMPLE_SELECT;

public DrawControl(MapLibre map) {
this.map = map;
injectScript();
map.addAttachListener(e -> {
doInit();
});
if(map.isAttached()) {
doInit();
}
}

private void doInit() {
map.js("""
const drawOptions = {
defaultMode : "%s",
displayControlsDefault: false
};
const id = "%s";
const draw = new MapboxDraw(drawOptions);
map[id] = draw;
map.addControl(draw);
map.on("draw.modechange", e => {
// TODO figure out a proper way for cursors
if(e.mode == "direct_select") {
map._canvasContainer.style.cursor = "default";
} else {
map._canvasContainer.style.cursor = "";
}
const evt = new Event("modechange");
evt.mode = e.mode;
component.dispatchEvent(evt);
});
""".formatted(mode.toString().toLowerCase(), id));
}

public void setMode(DrawMode mode) {
this.mode = mode;
if(map.isAttached()) {
js("""
const mode = "$mode";
draw.changeMode(mode);
if(mode.indexOf("draw") != -1 ) {
map._canvasContainer.style.cursor = "default";
}
""", Map.of("mode", mode.toString().toLowerCase()));
} else {
js("""
if($mode.indexOf("draw") != -1 ) {
map._canvasContainer.style.cursor = "default";
}
""", Map.of("mode", mode.toString().toLowerCase()));

}
}

protected void injectScript() {
JSLoader.loadFiles(map, "https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-draw/v1.4.3/mapbox-gl-draw.js", "mapbox-gl-draw", "v1.4.3");
}

private PendingJavaScriptResult js(String js, Map args) {
js = "const draw = map['%s'];\n".formatted(id) + js;
return map.js(js,args);
}

public CompletableFuture<GeometryCollection> getAll() {
var v = new CompletableFuture<GeometryCollection>();
js("""
const all = draw.getAll();
return JSON.stringify(all);
""", Collections.emptyMap()).then(String.class, str -> {
try {
GeometryCollection geom = (GeometryCollection) new GeoJsonReader().read(str);
v.complete(geom);
} catch (ParseException e) {
v.completeExceptionally(e);
}
});
return v;
}

}
56 changes: 56 additions & 0 deletions src/test/java/org/vaadin/addons/maplibre/DrawControlView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.vaadin.addons.maplibre;

import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.radiobutton.RadioButtonGroup;
import com.vaadin.flow.router.Route;
import org.vaadin.firitin.components.button.VButton;
import org.vaadin.firitin.components.orderedlayout.VHorizontalLayout;
import org.vaadin.firitin.components.orderedlayout.VVerticalLayout;

@Route
public class DrawControlView extends VVerticalLayout {

public DrawControlView() {
MapLibre map = new MapLibre("https://api.maptiler.com/maps/streets/style.json?key=G5n7stvZjomhyaVYP0qU");
DrawControl drawControl = new DrawControl(map);
addAndExpand(map);

var toolbar = new VHorizontalLayout();

// TODO disabled items in Viritin
// EnumSelect<DrawControl.DrawMode> modeSelect = new EnumSelect<>(DrawControl.DrawMode.class);
RadioButtonGroup<DrawControl.DrawMode> modeSelect = new RadioButtonGroup<>();
modeSelect.setItems(DrawControl.DrawMode.values());
modeSelect.setItemEnabledProvider(item -> {
if(item == DrawControl.DrawMode.DIRECT_SELECT) {
return false; // needs id
}
return true;
});
modeSelect.addValueChangeListener(e -> {
if(e.isFromClient())
drawControl.setMode(e.getValue());
});
modeSelect.setValue(DrawControl.DrawMode.SIMPLE_SELECT);

// TODO figure out what is wrong with selecting drawing
// modeSelect.setValue(DrawControl.DrawMode.DRAW_POLYGON);
// drawControl.setMode(DrawControl.DrawMode.DRAW_POLYGON);
// TODO figure out by setting mode with keyboard shortcut fails

toolbar.add(modeSelect);

drawControl.addModeChangeListener(e -> {
modeSelect.setValue(e.getDrawMode());
});

toolbar.add(new VButton("Show geometries", buttonClickEvent -> {
drawControl.getAll().thenAccept(geometryCollection -> {
Notification.show("Drawn geometries:" + geometryCollection.toText());
});

}));

add(toolbar);
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
package org.vaadin.addons.maplibre;

import com.vaadin.flow.component.ClientCallable;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.KeyModifier;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.dependency.JavaScript;
import com.vaadin.flow.component.dependency.StyleSheet;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.Polygon;
Expand All @@ -23,16 +17,15 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

@Route
@JavaScript("https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-draw/v1.4.3/mapbox-gl-draw.js")
//@StyleSheet("https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-draw/v1.4.2/mapbox-gl-draw.css")
public class DrawingTests extends VVerticalLayout {
public class RawDrawingTests extends VVerticalLayout {
private final MapLibre map;

public DrawingTests() {
public RawDrawingTests() {
add(new RichText().withMarkDown("""
# Drawing geometries with mapbox-gl-draw
Expand Down

0 comments on commit 3c4ad0f

Please sign in to comment.