diff --git a/client/src/main/scala/ch/wsl/box/client/RoutingRegistryDef.scala b/client/src/main/scala/ch/wsl/box/client/RoutingRegistryDef.scala
index 6dd107c8..84171197 100755
--- a/client/src/main/scala/ch/wsl/box/client/RoutingRegistryDef.scala
+++ b/client/src/main/scala/ch/wsl/box/client/RoutingRegistryDef.scala
@@ -53,7 +53,9 @@ class RoutingRegistryDef extends RoutingRegistry[RoutingState] with Logging {
case "/box" / "function" / exportFunction => DataState(DataKind.FUNCTION,exportFunction)
case "/box" / kind / entity / "page" => FormPageState(kind,entity,"true",false,Layouts.std)
case "/box" / kind / entity / "insert" => EntityFormState(kind,entity,"true",None,false,Layouts.std)
+ case "/box" / kind / entity / "insert" / layout => EntityFormState(kind,entity,"true",None,false,layout)
case "/box" / kind / entity / "row" / write / id => EntityFormState(kind,entity,write,Some(id),false,Layouts.std)
+ case "/box" / kind / entity / "row" / write / id / layout => EntityFormState(kind,entity,write,Some(id),false,layout)
case "/box" / kind / entity / "child" / childEntity => MasterChildState(kind,entity,childEntity)
case "/box" / kind / entity => EntityTableState(kind,entity,None,false)
case "/box" / kind / entity / "query" / query => EntityTableState(kind,entity,Some(query),false)
diff --git a/client/src/main/scala/ch/wsl/box/client/geo/StandaloneMap.scala b/client/src/main/scala/ch/wsl/box/client/geo/StandaloneMap.scala
index 6373a2b2..4e150389 100644
--- a/client/src/main/scala/ch/wsl/box/client/geo/StandaloneMap.scala
+++ b/client/src/main/scala/ch/wsl/box/client/geo/StandaloneMap.scala
@@ -131,6 +131,7 @@ abstract class StandaloneMap(_div:Div, metadata:MapMetadata,properties:ReadableP
}
}
def redrawControl():Unit = {
+ logger.debug("drawControl")
window.setTimeout(() => {
controlsDiv.children.toSeq.foreach(controlsDiv.removeChild)
controlsBottomDiv.children.toSeq.foreach(controlsBottomDiv.removeChild)
@@ -215,9 +216,7 @@ abstract class StandaloneMap(_div:Div, metadata:MapMetadata,properties:ReadableP
}
case _ => None
}
- layersExtent.tail.foreach{ e =>
- extentMod.extend(layersExtent.head,e)
- }
+
if (layersExtent.isEmpty) {
None
} else {
@@ -324,18 +323,19 @@ abstract class StandaloneMap(_div:Div, metadata:MapMetadata,properties:ReadableP
ready.set(false)
- for {
+ {for {
baseLayers <- Future.sequence(metadata.db.filter(_.autofocus).map(v => dbVectorLayer(v, d, None)))
_ = addLayers(baseLayers.map(x => geomsToLayer(x._1, x._2)),true)
extent = fit()
extraLayers <- Future.sequence(metadata.db.filterNot(_.autofocus).map(v => dbVectorLayer(v, d, Some(extent))))
} yield {
-
+ logger.debug("loaded layers")
selectedLayerForEdit.set(selectedLayerForEdit.get, true) // retrigger layer listener
addLayers(extraLayers.map(x => geomsToLayer(x._1, x._2)),false)
redrawControl()
ready.set(true)
- }
+ logger.debug("ready")
+ }}.recover{ case t => t.printStackTrace()}
}
properties.listen({d =>
diff --git a/client/src/main/scala/ch/wsl/box/client/views/EntityFormView.scala b/client/src/main/scala/ch/wsl/box/client/views/EntityFormView.scala
index 4f52f2b5..9a6f1605 100755
--- a/client/src/main/scala/ch/wsl/box/client/views/EntityFormView.scala
+++ b/client/src/main/scala/ch/wsl/box/client/views/EntityFormView.scala
@@ -52,11 +52,11 @@ import scala.util.{Failure, Success, Try}
*/
case class EntityFormModel(name:String, kind:String, id:Option[String], metadata:Option[JSONMetadata], originalData:Json,data:Json,
- error:String, children:Seq[JSONMetadata], navigation: Navigation, changed:Boolean, write:Boolean, public:Boolean, insert:Boolean, showActionPanelMobile: Boolean, localData:Boolean)
+ error:String, children:Seq[JSONMetadata], navigation: Navigation, changed:Boolean, write:Boolean, public:Boolean, insert:Boolean, showActionPanelMobile: Boolean, localData:Boolean, updateRight:Boolean)
object EntityFormModel extends HasModelPropertyCreator[EntityFormModel] {
- val empty = EntityFormModel("","",None,None,Json.Null,Json.Null,"",Seq(), Navigation.empty0,false, true, false, true, false,false)
+ val empty = EntityFormModel("","",None,None,Json.Null,Json.Null,"",Seq(), Navigation.empty0,false, true, false, true, false,false,false)
implicit val blank: Blank[EntityFormModel] = Blank.Simple(empty)
}
@@ -146,7 +146,8 @@ case class EntityFormPresenter(model:ModelProperty[EntityFormModel]) extends Pre
state.public,
insert,
false,
- localData = record.local_version
+ localData = record.local_version,
+ updateRight = rowAccess
)
model.set(stateModel)
@@ -724,7 +725,10 @@ case class EntityFormView(model:ModelProperty[EntityFormModel], presenter:Entity
}
- if((action.updateOnly && _id.isDefined && !model.subProp(_.insert).get) || (action.insertOnly && model.subProp(_.insert).get) || (!action.insertOnly && !action.updateOnly)) {
+ if(
+ (!action.needUpdateRight || model.subProp(_.updateRight).get) &&
+ ((action.updateOnly && _id.isDefined && !model.subProp(_.insert).get) || (action.insertOnly && model.subProp(_.insert).get) || (!action.insertOnly && !action.updateOnly))
+ ) {
val actionButton = button(
id := TestHooks.actionButton(action.label),
importance,
diff --git a/client/src/main/scala/ch/wsl/box/client/views/EntityTableView.scala b/client/src/main/scala/ch/wsl/box/client/views/EntityTableView.scala
index e620b71e..364aef7a 100755
--- a/client/src/main/scala/ch/wsl/box/client/views/EntityTableView.scala
+++ b/client/src/main/scala/ch/wsl/box/client/views/EntityTableView.scala
@@ -16,7 +16,7 @@ import ch.wsl.box.client.views.components.ui.TwoPanelResize
import ch.wsl.box.client.views.components.widget.DateTimeWidget
import ch.wsl.box.client.views.components.{Debug, MapList, TableFieldsRenderer}
import ch.wsl.box.client.views.elements.Offline
-import ch.wsl.box.client.views.helpers.TableColumnDrag
+import ch.wsl.box.client.views.helpers.{TableColumnDrag}
import ch.wsl.box.model.shared.EntityKind.VIEW
import ch.wsl.box.model.shared.GeoJson.Polygon
import ch.wsl.box.model.shared.geo.GeoDataRequest
@@ -372,7 +372,7 @@ case class EntityTablePresenter(model:ModelProperty[EntityTableModel], onSelect:
(ta.target, new_window, url) match {
case (_,_, None) => ()
case (Self, false, Some(url)) => Navigate.toUrl(url)
- case (_,_, Some(url)) => window.open(url)
+ case (NewWindow,_, Some(url)) => window.open(url)
}
true
}
diff --git a/db/box_migrations/BOX_V68__form_action_rights.sql b/db/box_migrations/BOX_V68__form_action_rights.sql
new file mode 100644
index 00000000..0f78ee18
--- /dev/null
+++ b/db/box_migrations/BOX_V68__form_action_rights.sql
@@ -0,0 +1,6 @@
+
+alter table form_actions add need_update_right boolean default false not null;
+alter table form_actions add need_delete_right boolean default false not null;
+
+alter table form_navigation_actions add need_update_right boolean default false not null;
+alter table form_navigation_actions add need_delete_right boolean default false not null;
\ No newline at end of file
diff --git a/project/Settings.scala b/project/Settings.scala
index cc486694..78e9b6cc 100755
--- a/project/Settings.scala
+++ b/project/Settings.scala
@@ -171,6 +171,9 @@ object Settings {
"org.geotools" % "gt-shapefile" % "34.0",
"org.geotools" % "gt-epsg-hsql" % "34.0",
"org.geotools" % "gt-geopkg" % "34.0",
+ "org.geotools" % "gt-wms" % "34.0",
+ "org.geotools" % "gt-wmts" % "34.0",
+ "org.geotools.xsd" % "gt-xsd-sld" % "34.0",
"com.google.zxing" % "core" % "3.5.0",
"com.google.zxing" % "javase" % "3.5.0",
"com.typesafe" %% "ssl-config-core" % "0.6.1",
diff --git a/server/src/main/scala/ch/wsl/box/model/boxentities/BoxForm.scala b/server/src/main/scala/ch/wsl/box/model/boxentities/BoxForm.scala
index 328adcb7..c3813655 100755
--- a/server/src/main/scala/ch/wsl/box/model/boxentities/BoxForm.scala
+++ b/server/src/main/scala/ch/wsl/box/model/boxentities/BoxForm.scala
@@ -120,12 +120,14 @@ object BoxForm {
html_check:Boolean = true,
view_enable:Boolean = true,
edit_enable:Boolean = true,
+ need_update_right:Boolean = false,
+ need_delete_right:Boolean = false,
target:Option[String] = None,
enabled_roles:Option[List[String]] = None
)
class BoxForm_actions(_tableTag: Tag) extends profile.api.Table[BoxForm_actions_row](_tableTag,schema, "form_actions") {
- def * = (Rep.Some(uuid), form_uuid, action, importance, after_action_goto, label, update_only, insert_only, reload, action_order,confirm_text,execute_function,condition,html_check,view_enable,edit_enable,target,enabled_roles) <> (BoxForm_actions_row.tupled, BoxForm_actions_row.unapply)
+ def * = (Rep.Some(uuid), form_uuid, action, importance, after_action_goto, label, update_only, insert_only, reload, action_order,confirm_text,execute_function,condition,html_check,view_enable,edit_enable,need_update_right,need_delete_right,target,enabled_roles) <> (BoxForm_actions_row.tupled, BoxForm_actions_row.unapply)
val uuid: Rep[java.util.UUID] = column[java.util.UUID]("uuid", O.AutoInc, O.PrimaryKey)
val form_uuid: Rep[java.util.UUID] = column[java.util.UUID]("form_uuid")
@@ -143,6 +145,8 @@ object BoxForm {
val condition: Rep[Option[Json]] = column[Option[Json]]("condition")
val view_enable: Rep[Boolean] = column[Boolean]("view_enable")
val edit_enable: Rep[Boolean] = column[Boolean]("edit_enable")
+ val need_update_right: Rep[Boolean] = column[Boolean]("need_update_right")
+ val need_delete_right: Rep[Boolean] = column[Boolean]("need_delete_right")
val html_check: Rep[Boolean] = column[Boolean]("html_check")
val enabled_roles: Rep[Option[List[String]]] = column[Option[List[String]]]("enabled_roles")
@@ -167,10 +171,13 @@ object BoxForm {
view_enable:Boolean = true,
edit_enable:Boolean = true,
+
+ need_update_right:Boolean = false,
+ need_delete_right:Boolean = false,
)
class BoxForm_navigation_actions(_tableTag: Tag) extends profile.api.Table[BoxForm_navigation_actions_row](_tableTag,schema, "form_navigation_actions") {
- def * = (Rep.Some(uuid), form_uuid, action, importance, after_action_goto, label, update_only, insert_only, reload, action_order,confirm_text,execute_function,enabled_roles,view_enable,edit_enable) <> (BoxForm_navigation_actions_row.tupled, BoxForm_navigation_actions_row.unapply)
+ def * = (Rep.Some(uuid), form_uuid, action, importance, after_action_goto, label, update_only, insert_only, reload, action_order,confirm_text,execute_function,enabled_roles,view_enable,edit_enable,need_update_right,need_delete_right) <> (BoxForm_navigation_actions_row.tupled, BoxForm_navigation_actions_row.unapply)
val uuid: Rep[java.util.UUID] = column[java.util.UUID]("uuid", O.AutoInc, O.PrimaryKey)
val form_uuid: Rep[java.util.UUID] = column[java.util.UUID]("form_uuid")
@@ -187,6 +194,8 @@ object BoxForm {
val enabled_roles: Rep[Option[List[String]]] = column[Option[List[String]]]("enabled_roles")
val view_enable: Rep[Boolean] = column[Boolean]("view_enable")
val edit_enable: Rep[Boolean] = column[Boolean]("edit_enable")
+ val need_update_right: Rep[Boolean] = column[Boolean]("need_update_right")
+ val need_delete_right: Rep[Boolean] = column[Boolean]("need_delete_right")
/** Foreign key referencing Field (database name fkey_field) */
lazy val fieldFk = foreignKey("fkey_form", form_uuid, BoxFormTable)(r => r.form_uuid, onUpdate=ForeignKeyAction.Cascade, onDelete=ForeignKeyAction.Cascade)
diff --git a/server/src/main/scala/ch/wsl/box/rest/io/geotools/GeoPackageWriter.scala b/server/src/main/scala/ch/wsl/box/rest/io/geotools/GeoPackageWriter.scala
index 522c0f79..fb3d05f9 100644
--- a/server/src/main/scala/ch/wsl/box/rest/io/geotools/GeoPackageWriter.scala
+++ b/server/src/main/scala/ch/wsl/box/rest/io/geotools/GeoPackageWriter.scala
@@ -18,24 +18,7 @@ import scala.concurrent.{ExecutionContext, Future}
object GeoPackageWriter {
- def geomSchema(builder:SimpleFeatureTypeBuilder,name:String,geo:Seq[Option[GeoJson.Geometry]],srid:Int) = {
- geo.toList.flatten.filterNot(_ == GeoJson.Empty).headOption match {
- case Some(value) => {
- value match {
- case geometry: GeoJson.SingleGeometry => geometry match {
- case GeoJson.Point(_, crs) => builder.add(name, classOf[Point], crs.srid)
- case GeoJson.LineString(_, crs) => builder.add(name, classOf[LineString], crs.srid)
- case GeoJson.Polygon(_, crs) => builder.add(name, classOf[Polygon], crs.srid)
- }
- case GeoJson.MultiPoint(_, crs) => builder.add(name, classOf[MultiPoint], crs.srid)
- case GeoJson.MultiLineString(_, crs) => builder.add(name, classOf[MultiLineString], crs.srid)
- case GeoJson.MultiPolygon(_, crs) => builder.add(name, classOf[MultiPolygon], crs.srid)
- case GeoJson.GeometryCollection(_, crs) => builder.add(name, classOf[Geometry], crs.srid)
- }
- }
- case None => builder.add(name, classOf[Geometry],srid)
- }
- }
+
private def fieldWriter(data:Json, typ: String, featureBuilder: SimpleFeatureBuilder) = {
typ match {
@@ -115,7 +98,7 @@ object GeoPackageWriter {
t.typ match {
case JSONFieldTypes.INTEGER => builder.add(n, classOf[Integer])
case JSONFieldTypes.NUMBER => builder.add(n, classOf[Double])
- case JSONFieldTypes.GEOMETRY if geomNameTrim(geomFieldName) == geomNameTrim(n) => geomSchema(builder, geomName, values.map(x => Some(x._1)), srid)
+ case JSONFieldTypes.GEOMETRY if geomNameTrim(geomFieldName) == geomNameTrim(n) => GeotoolUtils.geomSchema(builder, geomName, values.map(x => Some(x._1)), srid)
case JSONFieldTypes.GEOMETRY => ()
case _ => builder.add(n, classOf[String])
}
diff --git a/server/src/main/scala/ch/wsl/box/rest/io/geotools/GeotoolUtils.scala b/server/src/main/scala/ch/wsl/box/rest/io/geotools/GeotoolUtils.scala
new file mode 100644
index 00000000..09aeb6de
--- /dev/null
+++ b/server/src/main/scala/ch/wsl/box/rest/io/geotools/GeotoolUtils.scala
@@ -0,0 +1,28 @@
+package ch.wsl.box.rest.io.geotools
+
+import ch.wsl.box.model.shared.GeoJson
+import org.geotools.feature.simple.SimpleFeatureTypeBuilder
+import org.locationtech.jts.geom.{Geometry, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon}
+
+object GeotoolUtils {
+
+ def geomSchema(builder:SimpleFeatureTypeBuilder,name:String,geo:Seq[Option[GeoJson.Geometry]],srid:Int) = {
+ geo.toList.flatten.filterNot(_ == GeoJson.Empty).headOption match {
+ case Some(value) => {
+ value match {
+ case geometry: GeoJson.SingleGeometry => geometry match {
+ case GeoJson.Point(_, crs) => builder.add(name, classOf[Point], crs.srid)
+ case GeoJson.LineString(_, crs) => builder.add(name, classOf[LineString], crs.srid)
+ case GeoJson.Polygon(_, crs) => builder.add(name, classOf[Polygon], crs.srid)
+ }
+ case GeoJson.MultiPoint(_, crs) => builder.add(name, classOf[MultiPoint], crs.srid)
+ case GeoJson.MultiLineString(_, crs) => builder.add(name, classOf[MultiLineString], crs.srid)
+ case GeoJson.MultiPolygon(_, crs) => builder.add(name, classOf[MultiPolygon], crs.srid)
+ case GeoJson.GeometryCollection(_, crs) => builder.add(name, classOf[Geometry], crs.srid)
+ }
+ }
+ case None => builder.add(name, classOf[Geometry],srid)
+ }
+ }
+
+}
diff --git a/server/src/main/scala/ch/wsl/box/rest/io/geotools/Map2Image.scala b/server/src/main/scala/ch/wsl/box/rest/io/geotools/Map2Image.scala
new file mode 100644
index 00000000..7075ab80
--- /dev/null
+++ b/server/src/main/scala/ch/wsl/box/rest/io/geotools/Map2Image.scala
@@ -0,0 +1,170 @@
+package ch.wsl.box.rest.io.geotools
+
+import ch.wsl.box.model.shared.GeoJson.Geometry
+import ch.wsl.box.model.shared.geo.Box2d
+import org.geotools.api.data.SimpleFeatureSource
+import org.geotools.api.referencing.crs.CoordinateReferenceSystem
+import org.geotools.api.style.{NamedLayer, Style, StyledLayerDescriptor}
+import org.geotools.data.collection.{CollectionFeatureSource, ListFeatureCollection}
+import org.geotools.factory.CommonFactoryFinder
+import org.geotools.feature.DefaultFeatureCollection
+import org.geotools.feature.simple.{SimpleFeatureBuilder, SimpleFeatureTypeBuilder}
+import org.geotools.geometry.jts.ReferencedEnvelope
+import org.geotools.map.{FeatureLayer, MapContent, MessageDirectLayer}
+import org.geotools.ows.wms.WebMapServer
+import org.geotools.ows.wms.map.WMSLayer
+import org.geotools.ows.wmts.WebMapTileServer
+import org.geotools.ows.wmts.map.WMTSMapLayer
+import org.geotools.ows.wmts.model.WMTSLayer
+import org.geotools.renderer.lite.StreamingRenderer
+import org.geotools.sld.v1_1.{SLD, SLDConfiguration}
+import org.geotools.styling.BasicPolygonStyle
+import org.geotools.xml.styling.SLDParser
+import org.geotools.xsd.DOMParser
+import org.w3c.dom.Document
+
+import java.awt.{Color, Rectangle, RenderingHints}
+import java.awt.image.BufferedImage
+import java.io.{ByteArrayOutputStream, StringReader}
+import collection.JavaConverters._
+import java.net.URL
+import javax.imageio.ImageIO
+import javax.xml.parsers.DocumentBuilderFactory
+import scala.concurrent.{ExecutionContext, Future}
+
+object Map2Image {
+
+ private def renderMapToPNG(mapContent: MapContent, width: Int, height: Int,bbox:Box2d,crs:CoordinateReferenceSystem, copyright:String): Array[Byte] = {
+ val image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)
+ val graphics = image.createGraphics
+
+ val java2dHints =
+ new RenderingHints(
+ RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON
+ )
+
+
+ val rendererHints =
+ new java.util.HashMap[AnyRef, Any]()
+
+
+ rendererHints.put(
+ StreamingRenderer.LINE_WIDTH_OPTIMIZATION_KEY,
+ java.lang.Boolean.FALSE
+ )
+
+ rendererHints.put(
+ StreamingRenderer.VECTOR_RENDERING_KEY,
+ java.lang.Boolean.TRUE
+ )
+
+ // Set the background color (optional)
+ graphics.setBackground(Color.WHITE)
+ graphics.clearRect(0, 0, width, height)
+ // Create a renderer
+ val renderer = new StreamingRenderer
+ renderer.setJava2DHints(java2dHints)
+ renderer.setRendererHints(rendererHints)
+ renderer.setMapContent(mapContent)
+ // Set up the rendering context
+ val paintArea = new Rectangle(0, 0, width, height)
+
+ val envelope = new ReferencedEnvelope(bbox.xMin,bbox.xMax,bbox.yMin,bbox.yMax,crs)
+
+ renderer.paint(graphics, paintArea, envelope)
+
+ graphics.setColor(Color.white)
+ graphics.fillRect(0,height - 30,width,30)
+ graphics.setColor(Color.DARK_GRAY)
+ graphics.drawString(copyright, 0, height - 15)
+
+
+
+ val byteArrayOutputStream = new ByteArrayOutputStream
+
+ // Save the image to file
+ ImageIO.write(image, "png", byteArrayOutputStream)
+ // Clean up
+ graphics.dispose()
+ mapContent.dispose()
+
+ byteArrayOutputStream.toByteArray()
+ }
+
+ private def loadStyle(sld:String):Seq[Style] = {
+ val styleFactory = CommonFactoryFinder.getStyleFactory()
+
+ // Create a DocumentBuilder to parse the XML String
+ val factory = DocumentBuilderFactory.newInstance
+ factory.setNamespaceAware(true)
+ val builder = factory.newDocumentBuilder
+
+ val source = new org.xml.sax.InputSource(new StringReader(sld))
+
+ val document = builder.parse(source)
+
+ val sldParser = new SLDParser(styleFactory)
+
+ sldParser.readDOM(document)
+
+ val parser = new DOMParser(
+ new SLDConfiguration(),
+ document
+ )
+
+ val sldParsed = parser.parse().asInstanceOf[StyledLayerDescriptor]
+ sldParsed.getStyledLayers().head.asInstanceOf[NamedLayer].getStyles
+
+
+
+ }
+
+ def renderPng(wmtsServer:String,layerName:String,width:Int,height:Int, bbox:Box2d,geoms:Seq[Geometry],sld:String)(implicit ex:ExecutionContext):Future[Array[Byte]] = Future{
+
+ val url = new URL(wmtsServer);
+
+ val mapcontent = new MapContent
+
+ val wmts = new WebMapTileServer(url)
+ val capabilities = wmts.getCapabilities()
+
+ // gets all the layers in a flat list, in the order they appear in
+ // the capabilities document (so the rootLayer is at index 0)
+ val layer = capabilities.getLayer(layerName)
+
+ val copyright = capabilities.getService.getContactInformation.getOrganisationName.toString()
+
+ val crs = capabilities.getMatrixSets.asScala.head.getCoordinateReferenceSystem
+
+
+ mapcontent.setTitle(wmts.getCapabilities.getService.getTitle)
+ mapcontent.addLayer(new WMTSMapLayer(wmts, layer))
+
+
+ // Create vector schema
+ val builder: SimpleFeatureTypeBuilder = new SimpleFeatureTypeBuilder
+ builder.setName("geom")
+ GeotoolUtils.geomSchema(builder,"geom",geoms.map(x => Some(x)),geoms.headOption.map(_.crs.srid).getOrElse(0))
+ val schema = builder.buildFeatureType()
+ val collection = new DefaultFeatureCollection("geoms", schema)
+ val featureBuilder = new SimpleFeatureBuilder(schema)
+
+ // Add geoms
+ geoms.flatMap(GeoJsonConverter.toJTS).foreach{ g =>
+ featureBuilder.add(g)
+ collection.add(featureBuilder.buildFeature(null))
+ }
+ val source = new CollectionFeatureSource(collection)
+
+
+ // Load SLD style
+ val styles = loadStyle(sld)
+
+ // Add vector layer
+ mapcontent.addLayer(new FeatureLayer(source,styles.head))
+
+ renderMapToPNG(mapcontent,width,height,bbox,crs,copyright)
+
+ }
+}
diff --git a/server/src/main/scala/ch/wsl/box/rest/io/geotools/SLD.scala b/server/src/main/scala/ch/wsl/box/rest/io/geotools/SLD.scala
new file mode 100644
index 00000000..c66f68aa
--- /dev/null
+++ b/server/src/main/scala/ch/wsl/box/rest/io/geotools/SLD.scala
@@ -0,0 +1,60 @@
+package ch.wsl.box.rest.io.geotools
+
+object SLD {
+ def defaultPoint = """
+ |
+ |
+ | New scratch layer
+ |
+ | New scratch layer
+ |
+ |
+ | Single symbol
+ |
+ |
+ |
+ | circle
+ |
+ | #db1e2a
+ |
+ |
+ | #801119
+ | 1
+ |
+ |
+ | 14
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |""".stripMargin
+
+ def defaultPolygon = s"""
+ |
+ |
+ | New scratch layer
+ |
+ | New scratch layer
+ |
+ |
+ | Single symbol
+ |
+ |
+ | #e41a1c
+ | 0.47
+ |
+ |
+ | #800e10
+ | 1
+ | bevel
+ |
+ |
+ |
+ |
+ |
+ |
+ |""".stripMargin
+}
diff --git a/server/src/main/scala/ch/wsl/box/rest/logic/functions/RuntimeFunction.scala b/server/src/main/scala/ch/wsl/box/rest/logic/functions/RuntimeFunction.scala
index 4d2ebaeb..0530f80c 100644
--- a/server/src/main/scala/ch/wsl/box/rest/logic/functions/RuntimeFunction.scala
+++ b/server/src/main/scala/ch/wsl/box/rest/logic/functions/RuntimeFunction.scala
@@ -8,6 +8,7 @@ import io.circe.Json
import scala.concurrent.{ExecutionContext, Future}
import ch.wsl.box.jdbc.PostgresProfile.api._
+import ch.wsl.box.model.shared.GeoJson.Geometry
import ch.wsl.box.model.shared.{DataResult, DataResultTable}
import ch.wsl.box.services.Services
@@ -25,6 +26,7 @@ trait RuntimePSQL{
trait RuntimeUtils{
def qrCode(url:String):String
+ def swissTopoMap(geometry:Option[Json], width:Int, height:Int, padding:Int = 500):String
}
case class Context(data:Json,ws:RuntimeWS,psql:RuntimePSQL,utils:RuntimeUtils)
diff --git a/server/src/main/scala/ch/wsl/box/rest/logic/functions/UtilsImpl.scala b/server/src/main/scala/ch/wsl/box/rest/logic/functions/UtilsImpl.scala
index f2822e69..8be1b4bc 100644
--- a/server/src/main/scala/ch/wsl/box/rest/logic/functions/UtilsImpl.scala
+++ b/server/src/main/scala/ch/wsl/box/rest/logic/functions/UtilsImpl.scala
@@ -1,4 +1,8 @@
package ch.wsl.box.rest.logic.functions
+import ch.wsl.box.model.shared.GeoJson
+import ch.wsl.box.model.shared.GeoJson.{CRS, Coordinates, Geometry, Point}
+import ch.wsl.box.model.shared.geo.Box2d
+import ch.wsl.box.rest.io.geotools.{Map2Image, SLD}
import com.google.zxing.BarcodeFormat
import com.google.zxing.client.j2se.MatrixToImageWriter
import com.google.zxing.qrcode.QRCodeWriter
@@ -8,8 +12,12 @@ import java.util.Base64
import javax.imageio.ImageIO
import com.google.zxing.EncodeHintType
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
+import io.circe.Json
import java.util
+import scala.concurrent.Await
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.concurrent.duration.DurationInt
object UtilsImpl extends RuntimeUtils {
override def qrCode(url: String): String = {
@@ -27,4 +35,52 @@ object UtilsImpl extends RuntimeUtils {
os.close()
result
}
+
+ override def swissTopoMap(geomJS: Option[Json], width:Int, height:Int, padding:Int = 500): String = {
+ import Geometry._
+ val geometry = geomJS.get.as[Geometry].toOption.get
+ val minX = geometry.allCoordinates.map(_.x).min
+ val maxX = geometry.allCoordinates.map(_.x).max
+ val minY = geometry.allCoordinates.map(_.y).min
+ val maxY = geometry.allCoordinates.map(_.y).max
+ val geomRatio = (maxX - minX + padding*2) / (maxY - minY + padding*2)
+ val reqRatio = width.toDouble / height
+ val ratio = geomRatio / reqRatio
+ val bbox = if(geomRatio == reqRatio) {
+ Box2d(minX - padding,minY - padding, maxX + padding, maxY + padding)
+ } else if(geomRatio > reqRatio) {
+ val adj = padding * ratio
+ Box2d(minX - padding,minY - adj, maxX + padding, maxY + adj)
+ } else {
+ val adj = padding / ratio
+ Box2d(minX - adj,minY - padding, maxX + adj, maxY + padding)
+ }
+
+ val style = geometry match {
+ case geometry: GeoJson.SingleGeometry => geometry match {
+ case GeoJson.Empty => SLD.defaultPoint
+ case Point(coordinates, crs) => SLD.defaultPoint
+ case GeoJson.LineString(coordinates, crs) => SLD.defaultPoint
+ case GeoJson.Polygon(coordinates, crs) => SLD.defaultPolygon
+ }
+ case GeoJson.MultiPoint(coordinates, crs) => SLD.defaultPoint
+ case GeoJson.MultiLineString(coordinates, crs) => SLD.defaultPoint
+ case GeoJson.MultiPolygon(coordinates, crs) => SLD.defaultPolygon
+ case GeoJson.GeometryCollection(geometries, crs) => SLD.defaultPoint
+ }
+
+
+ val fut = Map2Image.renderPng(
+ "https://wmts.geo.admin.ch/EPSG/2056/1.0.0/WMTSCapabilities.xml",
+ "ch.swisstopo.pixelkarte-farbe",
+ width,height,
+ bbox,
+ Seq(geometry),
+ style
+ )
+ val file = Await.result(fut,20.seconds)
+ Base64.getEncoder.encodeToString(file)
+ }
+
+
}
diff --git a/server/src/main/scala/ch/wsl/box/rest/metadata/BoxFormMetadataFactory.scala b/server/src/main/scala/ch/wsl/box/rest/metadata/BoxFormMetadataFactory.scala
index 10dc865b..9e6dec31 100644
--- a/server/src/main/scala/ch/wsl/box/rest/metadata/BoxFormMetadataFactory.scala
+++ b/server/src/main/scala/ch/wsl/box/rest/metadata/BoxFormMetadataFactory.scala
@@ -43,8 +43,8 @@ object BoxFormMetadataFactory extends Logging with MetadataFactory {
FormUIDef.field_static(tablesAndViews,functions.map(_.name),fields,roles),
FormUIDef.fieldI18n(services.config.langs,fields),
FormUIDef.formI18n(viewsOnly,services.config.langs),
- FormUIDef.form_actions(functions.map(_.name)),
- FormUIDef.form_navigation_actions(functions.map(_.name)),
+ FormUIDef.form_actions(functions.map(_.name),roles),
+ FormUIDef.form_navigation_actions(functions.map(_.name),roles),
FunctionUIDef.main,
FunctionUIDef.field(tablesAndViews),
FunctionUIDef.fieldI18n(services.config.langs),
@@ -92,8 +92,8 @@ object BoxFormMetadataFactory extends Logging with MetadataFactory {
FormUIDef.field_childs(forms,fields,roles),
FormUIDef.fieldI18n(services.config.langs,fields),
FormUIDef.formI18n(viewsOnly,services.config.langs),
- FormUIDef.form_actions(functions.map(_.name)),
- FormUIDef.form_navigation_actions(functions.map(_.name))
+ FormUIDef.form_actions(functions.map(_.name),roles),
+ FormUIDef.form_navigation_actions(functions.map(_.name),roles)
)
case f if f.objId == PAGE => Seq(FormUIDef.field_static(tablesAndViews,functions.map(_.name),fields,roles),FormUIDef.field_childs(forms,fields,roles),FormUIDef.fieldI18n(services.config.langs,fields),FormUIDef.formI18n(viewsOnly,services.config.langs))
case f if f.objId == FORM_FIELD => Seq(FormUIDef.fieldI18n(services.config.langs,fields))
diff --git a/server/src/main/scala/ch/wsl/box/rest/metadata/FormMetadataFactory.scala b/server/src/main/scala/ch/wsl/box/rest/metadata/FormMetadataFactory.scala
index 212441f1..04c2c8d7 100755
--- a/server/src/main/scala/ch/wsl/box/rest/metadata/FormMetadataFactory.scala
+++ b/server/src/main/scala/ch/wsl/box/rest/metadata/FormMetadataFactory.scala
@@ -218,6 +218,8 @@ object FormMetadataFactory extends Logging with MetadataFactory{
enabledRoles = a.enabled_roles,
view = a.view_enable,
edit = a.edit_enable,
+ needUpdateRight = a.need_update_right,
+ needDeleteRight = a.need_delete_right,
)
}.filter(services.config.localDb || _.action != SaveLocalAction),
navigationActions = navigationActions.map{a =>
@@ -234,6 +236,8 @@ object FormMetadataFactory extends Logging with MetadataFactory{
enabledRoles = a.enabled_roles,
view = a.view_enable,
edit = a.edit_enable,
+ needUpdateRight = a.need_update_right,
+ needDeleteRight = a.need_delete_right,
)
},
tableActions = if(tableActions.isEmpty && keys.nonEmpty) FormActionsMetadata.default.tableActions else {
diff --git a/server/src/main/scala/ch/wsl/box/rest/metadata/box/CommonField.scala b/server/src/main/scala/ch/wsl/box/rest/metadata/box/CommonField.scala
index 939e1027..6b3358a1 100644
--- a/server/src/main/scala/ch/wsl/box/rest/metadata/box/CommonField.scala
+++ b/server/src/main/scala/ch/wsl/box/rest/metadata/box/CommonField.scala
@@ -94,7 +94,7 @@ object CommonField {
))
).asPopup
- def roles(available_roles:Seq[String]) = JSONField(JSONFieldTypes.ARRAY_STRING,"roles",true,
+ def roles(available_roles:Seq[String], field:String = "roles") = JSONField(JSONFieldTypes.ARRAY_STRING,field,true,
widget = Some(WidgetsNames.multipleLookup),
lookup = Some(JSONFieldLookup.prefilled(available_roles.map(x => JSONLookup(Json.fromString(x),Seq(x)))))
)
diff --git a/server/src/main/scala/ch/wsl/box/rest/metadata/box/FormUIDef.scala b/server/src/main/scala/ch/wsl/box/rest/metadata/box/FormUIDef.scala
index e18a5c0c..9b459c01 100644
--- a/server/src/main/scala/ch/wsl/box/rest/metadata/box/FormUIDef.scala
+++ b/server/src/main/scala/ch/wsl/box/rest/metadata/box/FormUIDef.scala
@@ -614,7 +614,7 @@ object FormUIDef {
action = FormActionsMetadata.default
)
- def form_actions(functions:Seq[String]) = JSONMetadata(
+ def form_actions(functions:Seq[String],roles:Seq[String]) = JSONMetadata(
objId = FORM_ACTION,
kind = EntityKind.BOX_FORM.kind,
name = "Form action",
@@ -659,6 +659,11 @@ object FormUIDef {
),
JSONField(JSONFieldTypes.BOOLEAN,"update_only",false,widget = Some(WidgetsNames.checkbox), default = Some("false")),
JSONField(JSONFieldTypes.BOOLEAN,"insert_only",false,widget = Some(WidgetsNames.checkbox), default = Some("false")),
+ JSONField(JSONFieldTypes.BOOLEAN,"view_enable",false,widget = Some(WidgetsNames.checkbox), default = Some("true")),
+ JSONField(JSONFieldTypes.BOOLEAN,"edit_enable",false,widget = Some(WidgetsNames.checkbox), default = Some("true")),
+ JSONField(JSONFieldTypes.BOOLEAN,"need_update_right",false,widget = Some(WidgetsNames.checkbox), default = Some("false")),
+ JSONField(JSONFieldTypes.BOOLEAN,"need_delete_right",false,widget = Some(WidgetsNames.checkbox), default = Some("false")),
+ CommonField.roles(roles,"enabled_roles"),
JSONField(JSONFieldTypes.BOOLEAN,"reload",false,widget = Some(WidgetsNames.checkbox), default = Some("false")),
JSONField(JSONFieldTypes.BOOLEAN,"html_check",false,widget = Some(WidgetsNames.checkbox), default = Some("true")),
JSONField(JSONFieldTypes.STRING,"confirm_text",true,label=Some("Confirm text"),
@@ -692,6 +697,11 @@ object FormUIDef {
"label",
"update_only",
"insert_only",
+ "view_enable",
+ "edit_enable",
+ "need_update_right",
+ "need_delete_right",
+ "enabled_roles",
"reload",
"confirm_text",
"condition",
@@ -712,7 +722,7 @@ object FormUIDef {
)
- def form_navigation_actions(functions:Seq[String]) = JSONMetadata(
+ def form_navigation_actions(functions:Seq[String],roles:Seq[String]) = JSONMetadata(
objId = FORM_NAVIGATION_ACTION,
kind = EntityKind.BOX_FORM.kind,
name = "Form navigation action",
@@ -752,6 +762,11 @@ object FormUIDef {
JSONField(JSONFieldTypes.BOOLEAN,"update_only",false,widget = Some(WidgetsNames.checkbox), default = Some("false")),
JSONField(JSONFieldTypes.BOOLEAN,"insert_only",false,widget = Some(WidgetsNames.checkbox), default = Some("false")),
JSONField(JSONFieldTypes.BOOLEAN,"reload",false,widget = Some(WidgetsNames.checkbox), default = Some("false")),
+ JSONField(JSONFieldTypes.BOOLEAN,"view_enable",false,widget = Some(WidgetsNames.checkbox), default = Some("true")),
+ JSONField(JSONFieldTypes.BOOLEAN,"edit_enable",false,widget = Some(WidgetsNames.checkbox), default = Some("true")),
+ JSONField(JSONFieldTypes.BOOLEAN,"need_update_right",false,widget = Some(WidgetsNames.checkbox), default = Some("false")),
+ JSONField(JSONFieldTypes.BOOLEAN,"need_delete_right",false,widget = Some(WidgetsNames.checkbox), default = Some("false")),
+ CommonField.roles(roles,"enabled_roles"),
JSONField(JSONFieldTypes.STRING,"confirm_text",true,label=Some("Confirm text"),
tooltip = Some("Before running action show a popup that ask for confirmation with the following text (translated with gobal transaltion table)"),
widget = Some(WidgetsNames.input)
@@ -777,6 +792,11 @@ object FormUIDef {
"label",
"update_only",
"insert_only",
+ "view_enable",
+ "edit_enable",
+ "need_update_right",
+ "need_delete_right",
+ "enabled_roles",
"reload",
"confirm_text"
)),
diff --git a/server/src/test/scala/ch/wsl/box/rest/RuntimeFunctionSpec.scala b/server/src/test/scala/ch/wsl/box/rest/RuntimeFunctionSpec.scala
index 79384af8..bb3316ce 100644
--- a/server/src/test/scala/ch/wsl/box/rest/RuntimeFunctionSpec.scala
+++ b/server/src/test/scala/ch/wsl/box/rest/RuntimeFunctionSpec.scala
@@ -2,7 +2,7 @@ package ch.wsl.box.rest
import akka.actor.ActorSystem
import akka.stream.{ActorMaterializer, Materializer}
-import ch.wsl.box.model.shared.{DataResultTable, JSONQuery}
+import ch.wsl.box.model.shared.{DataResultTable, GeoJson, JSONQuery}
import ch.wsl.box.rest.logic._
import ch.wsl.box.rest.logic.functions.{Context, RuntimeFunction, RuntimePSQL, RuntimeUtils, RuntimeWS}
import ch.wsl.box.rest.utils.{Lang, UserProfile}
@@ -39,6 +39,8 @@ class RuntimeFunctionSpec extends BaseSpec {
},
new RuntimeUtils {
override def qrCode(url: String): String = ???
+
+ override def swissTopoMap(geometry:Option[Json], width:Int, height:Int, buffer:Int = 500): String = ???
}
)
diff --git a/shared/src/main/scala/ch/wsl/box/model/shared/FormActions.scala b/shared/src/main/scala/ch/wsl/box/model/shared/FormActions.scala
index 3a8ac048..9a9a1599 100644
--- a/shared/src/main/scala/ch/wsl/box/model/shared/FormActions.scala
+++ b/shared/src/main/scala/ch/wsl/box/model/shared/FormActions.scala
@@ -53,15 +53,17 @@ object Importance {
sealed trait Target
case object Self extends Target
case object NewWindow extends Target
+case object Popup extends Target
object Target {
def fromString(s:String):Target = s match {
case "Self" => Self
case "NewWindow" => NewWindow
+ case "Popup" => Popup
}
- def all = Seq(Self,NewWindow)
+ def all = Seq(Self,NewWindow,Popup)
}
case class FormAction(
diff --git a/shared/src/main/scala/ch/wsl/box/model/shared/GeoJson.scala b/shared/src/main/scala/ch/wsl/box/model/shared/GeoJson.scala
index 3f7e3746..74025ca3 100644
--- a/shared/src/main/scala/ch/wsl/box/model/shared/GeoJson.scala
+++ b/shared/src/main/scala/ch/wsl/box/model/shared/GeoJson.scala
@@ -60,6 +60,7 @@ object GeoJson {
object CRS {
def default = wgs84
def wgs84 = CRS("EPSG:4326")
+ def epsg(epsg:Int) = CRS(s"EPSG:$epsg")
}