Commit 528c56fa authored by Hallvard Trætteberg's avatar Hallvard Trætteberg
Browse files

Merge branch...

Merge branch 'issue-11-allow-user-to-enter-and-update-metadata-about-the-latlong-points' into 'master'

Issue 11 allow user to enter and update metadata about the latlong points

Closes #10 and #11

See merge request !6
parents 4adf95b1 7e45ac70
Pipeline #53471 passed with stage
in 2 minutes and 11 seconds
......@@ -18,6 +18,12 @@ public class MetaData {
*/
public final static String DESCRIPTION_PROPERTY = "description";
/**
* the list containing standard properties associated to location metadata
*/
public final static Collection<String> STD_PROPERTIES = List.of(NAME_PROPERTY, DESCRIPTION_PROPERTY);
private Collection<String> tags;
private List<String> properties;
......@@ -278,4 +284,49 @@ public class MetaData {
}
}
}
/**
* check if a certain property is custom or standard
* a property is standard when the name is found in STD_PROPERTIES
* @param propertyName the property name to check
* @return true if custom false if standard prop
*/
public static boolean isCustomProperty(String propertyName) {
return !(STD_PROPERTIES.contains(propertyName));
}
/**
* Convenience method to check if the metadata contains custom properties
* that is other properties than the name and description
* @return true if there are any properties apart from standard ones and false otherwise
*/
public boolean hasCustomProperties() {
for (int i = 0; i < this.properties.size(); i = i + 2) {
String propertyName = this.properties.get(i);
if(isCustomProperty(propertyName)) return true;
}
return false;
}
/**
* Convenience method to check if the metadata contains properties
* other than the ones given in a list
* @return true if there are any properties not in the list, false otherwise
*/
public boolean hasOtherProperties(String ... propertyNames) {
outer: for (int i = 0; i < this.properties.size(); i = i + 2) {
String propertyName = this.properties.get(i);
for (String knownPropertyName : propertyNames) {
if (propertyName.equals(knownPropertyName)) {
continue outer;
}
return true;
}
}
return false;
}
}
......@@ -27,6 +27,20 @@ application {
run {
args = ["http://localhost:8080/", "[[63.1, 11.2], [63.2, 11.0]]"]
/*args = ['http://localhost:8080/','[{"latitude":63.1,"longitude":11.2},' +
'{"latitude":63.2,"longitude":11.0},{"latitude":63.5,"longitude":11.5,"metaData":'+
'{"properties":[{"name":"name","value":"Awsome place"},{"name":"description","value":'+
'"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla placerat urna non aliquet '+
'imperdiet. Nullam tincidunt felis vel sem blandit viverra. Etiam non volutpat erat. '+
'In hac habitasse platea dictumst. In lacus quam, rutrum vel malesuada non, molestie eu '+
'velit. Donec ut vulputate tortor, id convallis enim. Mauris et ipsum volutpat, dictum '+
'risus sed, aliquet sapien. Nam congue fermentum porta. Nullam non odio consequat, laoreet '+
'est eget, egestas dui. Aliquam suscipit elit non nisi sagittis, nec ultrices leo condimentum. '+
'Maecenas vel ligula nec mi feugiat volutpat. Aenean semper nisi sed tortor maximus tristique. '+
'Vestibulum at mauris massa. Nulla laoreet, velit eu lobortis efficitur, tortor sem molestie '+
'massa, at pellentesque tortor elit a nibh. In vel orci vitae magna rhoncus pulvinar sit amet '+
'id erat."}]}}]'] */
}
// javafx specific way of specifying dependencies
......
package simpleex.ui;
import com.fasterxml.jackson.databind.ObjectMapper;
import fxmapcontrol.Location;
import fxmapcontrol.MapBase;
import fxmapcontrol.MapItemsControl;
import fxmapcontrol.MapNode;
import fxmapcontrol.MapProjection;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ListView;
import javafx.scene.control.Slider;
import simpleex.core.LatLong;
import simpleex.json.LatLongsModule;
/*
@startuml
class AbstractFxAppController
class LatLongs
class BorderPane
class "ListView<LatLong>" as ListView
class "fxmapcontrol.MapBase" as MapBase
BorderPane *--> ListView: "left"
BorderPane *--> MapBase: "center"
AbstractFxAppController --> MapBase: "mapView"
AbstractFxAppController --> ListView: "locationListView"
@enduml
*/
/**
* The controller for the app.
* @author hal
*
*/
public abstract class AbstractFxAppController {
private LatLongsDataAccess dataAccess;
protected LatLongsDataAccess getDataAccess() {
return dataAccess;
}
protected void setDataAccess(final LatLongsDataAccess dataAccess) {
this.dataAccess = dataAccess;
if (locationListView != null) {
updateLocationViewList(0);
}
}
@FXML
private ListView<LatLong> locationListView;
@FXML
private MapBase mapView;
private MapItemsControl<MapNode> markersParent;
private MapMarker marker = null;
private DraggableNodeController draggableMapController = null;
private DraggableNodeController draggableMarkerController = null;
@FXML
private Slider zoomSlider;
@FXML
private void initialize() {
// map stuff
// mapView.getChildren().add(MapTileLayer.getOpenStreetMapLayer());
zoomSlider.valueProperty()
.addListener((prop, oldValue, newValue) -> mapView.setZoomLevel(zoomSlider.getValue()));
zoomSlider.setValue(8);
markersParent = new MapItemsControl<MapNode>();
mapView.getChildren().add(markersParent);
draggableMapController = new DraggableNodeController(this::handleMapDragged);
draggableMapController.setImmediate(true);
draggableMapController.attach(mapView);
draggableMarkerController = new DraggableNodeController(this::handleMarkerDragged);
// the location list
locationListView.getSelectionModel().selectedIndexProperty()
.addListener((prop, oldValue, newValue) -> updateMapMarker(true));
}
private void handleMapDragged(final Node node, final double dx, final double dy) {
final MapProjection projection = mapView.getProjection();
final Point2D point = projection.locationToViewportPoint(mapView.getCenter());
final Location newCenter = projection.viewportPointToLocation(point.add(-dx, -dy));
mapView.setCenter(newCenter);
}
private void handleMarkerDragged(final Node node, final double dx, final double dy) {
final MapProjection projection = mapView.getProjection();
final Point2D point = projection.locationToViewportPoint(marker.getLocation());
final Location newLocation = projection.viewportPointToLocation(point.add(dx, dy));
dataAccess.setLatLong(locationListView.getSelectionModel().getSelectedIndex(),
location2LatLong(newLocation));
updateLocationViewListSelection(false);
}
private LatLong location2LatLong(final Location newLocation) {
return new LatLong(newLocation.getLatitude(), newLocation.getLongitude());
}
private void updateMapMarker(final boolean centerOnMarker) {
final int num = locationListView.getSelectionModel().getSelectedIndex();
if (num < 0) {
markersParent.getItems().clear();
if (draggableMarkerController != null) {
draggableMarkerController.detach(marker);
}
marker = null;
} else {
final LatLong latLong = dataAccess.getLatLong(num);
if (marker == null) {
marker = new MapMarker(latLong);
markersParent.getItems().add(marker);
if (draggableMarkerController != null) {
draggableMarkerController.attach(marker);
}
} else {
marker.setLocation(latLong);
}
if (centerOnMarker) {
mapView.setCenter(marker.getLocation());
}
}
}
@FXML
void handleAddLocation() {
final Location center = mapView.getCenter();
final LatLong latLong = location2LatLong(center);
final int pos = dataAccess.addLatLong(latLong);
updateLocationViewList(pos);
}
private void updateLocationViewListSelection(final Boolean updateMapMarker) {
final int selectedIndex = locationListView.getSelectionModel().getSelectedIndex();
locationListView.getItems().set(selectedIndex, dataAccess.getLatLong(selectedIndex));
if (updateMapMarker != null) {
updateMapMarker(updateMapMarker);
}
}
protected void updateLocationViewList(int selectedIndex) {
final LatLong[] latLongs = dataAccess.getAllLatLongs().toArray(new LatLong[0]);
final int oldSelectionIndex = locationListView.getSelectionModel().getSelectedIndex();
locationListView.setItems(FXCollections.observableArrayList(latLongs));
if (selectedIndex < 0 || selectedIndex >= latLongs.length) {
selectedIndex = oldSelectionIndex;
}
if (selectedIndex >= 0 && selectedIndex < latLongs.length) {
locationListView.getSelectionModel().select(selectedIndex);
}
}
private ObjectMapper objectMapper;
/**
* Gets the ObjectMapper used by this controller.
* @return the ObjectMapper used by this controller
*/
public ObjectMapper getObjectMapper() {
if (objectMapper == null) {
objectMapper = new ObjectMapper();
objectMapper.registerModule(new LatLongsModule());
}
return objectMapper;
}
protected void showExceptionDialog(final String message) {
final Alert alert = new Alert(AlertType.ERROR, message, ButtonType.CLOSE);
alert.showAndWait();
}
protected void showExceptionDialog(final String message, final Exception e) {
showExceptionDialog(message + ": " + e.getLocalizedMessage());
}
}
package simpleex.ui;
import java.util.Iterator;
import com.fasterxml.jackson.databind.ObjectMapper;
import fxmapcontrol.Location;
import fxmapcontrol.MapBase;
import fxmapcontrol.MapItemsControl;
import fxmapcontrol.MapNode;
import fxmapcontrol.MapProjection;
import javafx.collections.FXCollections;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ListView;
import javafx.scene.control.OverrunStyle;
import javafx.scene.control.Slider;
import javafx.scene.control.Tooltip;
import simpleex.core.LatLong;
import simpleex.core.MetaData;
import simpleex.json.LatLongsModule;
/*
@startuml
class AbstractFxAppController
class LatLongs
class BorderPane
class "ListView<LatLong>" as ListView
class "fxmapcontrol.MapBase" as MapBase
BorderPane *--> ListView: "left"
BorderPane *--> MapBase: "center"
AbstractFxAppController --> MapBase: "mapView"
AbstractFxAppController --> ListView: "locationListView"
@enduml
*/
/**
* The controller for the app.
* @author hal
*
*/
public abstract class AbstractFxAppController {
private LatLongsDataAccess dataAccess;
protected LatLongsDataAccess getDataAccess() {
return dataAccess;
}
protected void setDataAccess(final LatLongsDataAccess dataAccess) {
this.dataAccess = dataAccess;
if (locationListView != null) {
updateLocationViewList(0);
}
}
@FXML
private ListView<LatLong> locationListView;
@FXML
private MapBase mapView;
private MapItemsControl<MapNode> markersParent;
private MapMarker marker = null;
private DraggableNodeController draggableMapController = null;
private DraggableNodeController draggableMarkerController = null;
private Tooltip markerTooltip = null;
@FXML
private Slider zoomSlider;
@FXML
private void initialize() {
// map stuff
// mapView.getChildren().add(MapTileLayer.getOpenStreetMapLayer());
zoomSlider.valueProperty()
.addListener((prop, oldValue, newValue) -> mapView.setZoomLevel(zoomSlider.getValue()));
zoomSlider.setValue(8);
markersParent = new MapItemsControl<MapNode>();
mapView.getChildren().add(markersParent);
draggableMapController = new DraggableNodeController(this::handleMapDragged);
draggableMapController.setImmediate(true);
draggableMapController.attach(mapView);
draggableMarkerController = new DraggableNodeController(this::handleMarkerDragged);
// the location list
locationListView.getSelectionModel().selectedIndexProperty()
.addListener((prop, oldValue, newValue) -> updateMapMarker(true));
//connect the cell renderer to the list
locationListView.setCellFactory(listView -> new LatLongCell());
locationListView.addEventHandler(MetaDataEditorController.METADATA_SAVED, new EventHandler<MetaDataEvent>() {
@Override
public void handle(MetaDataEvent event) {
System.out.println("saved at list level");
}
});
}
private void handleMapDragged(final Node node, final double dx, final double dy) {
final MapProjection projection = mapView.getProjection();
final Point2D point = projection.locationToViewportPoint(mapView.getCenter());
final Location newCenter = projection.viewportPointToLocation(point.add(-dx, -dy));
mapView.setCenter(newCenter);
}
private void handleMarkerDragged(final Node node, final double dx, final double dy) {
final MapProjection projection = mapView.getProjection();
final Point2D point = projection.locationToViewportPoint(marker.getLocation());
final Location newLocation = projection.viewportPointToLocation(point.add(dx, dy));
dataAccess.setLatLong(locationListView.getSelectionModel().getSelectedIndex(),
location2LatLong(newLocation));
updateLocationViewListSelection(false);
}
private LatLong location2LatLong(final Location newLocation) {
return new LatLong(newLocation.getLatitude(), newLocation.getLongitude());
}
private void updateMapMarker(final boolean centerOnMarker) {
final int num = locationListView.getSelectionModel().getSelectedIndex();
if (num < 0) {
markersParent.getItems().clear();
if (draggableMarkerController != null) {
draggableMarkerController.detach(marker);
}
marker = null;
} else {
final LatLong latLong = dataAccess.getLatLong(num);
if (marker == null) {
marker = new MapMarker(latLong);
markersParent.getItems().add(marker);
if (draggableMarkerController != null) {
draggableMarkerController.attach(marker);
}
} else {
marker.setLocation(latLong);
}
if (centerOnMarker) {
mapView.setCenter(marker.getLocation());
}
}
prepareMarkerTooltip();
}
void prepareMarkerTooltip() {
if ((marker != null) && (this.markerTooltip != null)) {
Tooltip.uninstall(marker, markerTooltip);
}
Tooltip tooltip = new Tooltip();
tooltip.setPrefWidth(300.0);
tooltip.setWrapText(true);
tooltip.setTextOverrun(OverrunStyle.ELLIPSIS);
tooltip.setStyle(" -fx-background: rgba(255, 237, 131);\r\n" +
" -fx-text-fill: black;\r\n" +
" -fx-background-color: rgba(255, 237, 131, 0.8);\r\n" +
" -fx-background-radius: 6px;\r\n" +
" -fx-background-insets: 0;\r\n" +
" -fx-padding: 0.667em 0.75em 0.667em 0.75em; /* 10px */\r\n" +
" -fx-effect: dropshadow( three-pass-box , rgba(0,0,0,0.5) , 10, 0.0 , 0 , 3 );\r\n" +
" -fx-font-size: 0.85em;");
final int num = locationListView.getSelectionModel().getSelectedIndex();
if (num >= 0) {
final LatLong latLong = dataAccess.getLatLong(num);
String tooltipText = "";
if(latLong.hasMetaData()) {
if(latLong.getMetaData().hasProperty(MetaData.NAME_PROPERTY)) {
tooltipText += latLong.getMetaData().getProperty(MetaData.NAME_PROPERTY) +"\n";
}
tooltipText += latLong.toString()+"\n";
if(latLong.getMetaData().hasProperty(MetaData.DESCRIPTION_PROPERTY)) {
tooltipText += latLong.getMetaData().getProperty(MetaData.DESCRIPTION_PROPERTY) +"\n";
}
MetaData metaData = latLong.getMetaData();
final Iterator<String> props = metaData.propertyNames();
if (props.hasNext()) {
String propString = "\n";
while (props.hasNext()) {
String propName = props.next();
if((propName != MetaData.NAME_PROPERTY)
&&(propName != MetaData.DESCRIPTION_PROPERTY)) {
propString += propName + ":" + metaData.getProperty(propName) + "\n";
}
}
if(!propString.isBlank()) tooltipText += propString + "\n";
}
final Iterator<String> tags = metaData.tags();
if (tags.hasNext()) {
String tagString = "\n| ";
while (tags.hasNext()) {
tagString += tags.next() + " | ";
}
tooltipText += tagString + "\n";
}
} else {
tooltipText += latLong.toString();
}
tooltip.setText(tooltipText);
}
this.markerTooltip = tooltip;
Tooltip.install(marker, tooltip);
}
@FXML
void handleAddLocation() {
final Location center = mapView.getCenter();
final LatLong latLong = location2LatLong(center);
final int pos = dataAccess.addLatLong(latLong);
updateLocationViewList(pos);
}
private void updateLocationViewListSelection(final Boolean updateMapMarker) {
final int selectedIndex = locationListView.getSelectionModel().getSelectedIndex();
locationListView.getItems().set(selectedIndex, dataAccess.getLatLong(selectedIndex));
if (updateMapMarker != null) {
updateMapMarker(updateMapMarker);
}
}
protected void updateLocationViewList(int selectedIndex) {
final LatLong[] latLongs = dataAccess.getAllLatLongs().toArray(new LatLong[0]);
final int oldSelectionIndex = locationListView.getSelectionModel().getSelectedIndex();
locationListView.setItems(FXCollections.observableArrayList(latLongs));
if (selectedIndex < 0 || selectedIndex >= latLongs.length) {
selectedIndex = oldSelectionIndex;
}
if (selectedIndex >= 0 && selectedIndex < latLongs.length) {
locationListView.getSelectionModel().select(selectedIndex);
}
}
private ObjectMapper objectMapper;
/**
* Gets the ObjectMapper used by this controller.
* @return the ObjectMapper used by this controller
*/
public ObjectMapper getObjectMapper() {
if (objectMapper == null) {
objectMapper = new ObjectMapper();
objectMapper.registerModule(new LatLongsModule());
}
return objectMapper;
}
protected void showExceptionDialog(final String message) {
final Alert alert = new Alert(AlertType.ERROR, message, ButtonType.CLOSE);
alert.showAndWait();
}
protected void showExceptionDialog(final String message, final Exception e) {
showExceptionDialog(message + ": " + e.getLocalizedMessage());
}
}
package simpleex.ui;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import org.glassfish.grizzly.http.server.HttpServer;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import simpleex.core.LatLongs;
import simpleex.restapi.LatLongsService;
import simpleex.restserver.LatLongGrizzlyApp;
public class FxApp extends Application {
private HttpServer restServer = null;
@Override
public void start(final Stage stage) throws Exception {
URI baseUri = null;
final List<String> args = getParameters().getRaw();
if (args.size() >= 1) {
final List<String> serverArgs = new ArrayList<String>();
baseUri = URI.create(args.get(0));
serverArgs.add(baseUri.toString());
if (args.size() >= 2) {
// json of initial data
serverArgs.add(args.get(1));
}
restServer = LatLongGrizzlyApp.startServer(serverArgs.toArray(new String[serverArgs.size()]), 5);
}