diff --git a/livemap-demo/src/commonMain/kotlin/jetbrains/livemap/demo/FeaturesDemoModel.kt b/livemap-demo/src/commonMain/kotlin/jetbrains/livemap/demo/FeaturesDemoModel.kt index 680edfd75b7..d3e583f6b23 100644 --- a/livemap-demo/src/commonMain/kotlin/jetbrains/livemap/demo/FeaturesDemoModel.kt +++ b/livemap-demo/src/commonMain/kotlin/jetbrains/livemap/demo/FeaturesDemoModel.kt @@ -3,10 +3,9 @@ package jetbrains.livemap.demo import jetbrains.datalore.base.values.Color import jetbrains.datalore.visualization.base.canvas.CanvasControl import jetbrains.livemap.LiveMapSpec -import jetbrains.livemap.api.layers -import jetbrains.livemap.api.paths -import jetbrains.livemap.api.points +import jetbrains.livemap.api.* import jetbrains.livemap.demo.model.Cities.BOSTON +import jetbrains.livemap.demo.model.Cities.MOSCOW import jetbrains.livemap.demo.model.Cities.SPB import jetbrains.livemap.demo.model.GeoObject @@ -31,6 +30,29 @@ class FeaturesDemoModel(canvasControl: CanvasControl): DemoModelBase(canvasContr strokeWidth = 1.0 } } + + polygons { + polygon { + coordinates = listOf(BOSTON, SPB, MOSCOW).map(GeoObject::geoCoord) + fillColor = Color.LIGHT_CYAN + } + } + + hLines { + line { + lon = MOSCOW.lon + lat = MOSCOW.lat + lineDash = listOf(8.0, 8.0) + } + } + + vLines { + line { + lon = BOSTON.lon + lat = BOSTON.lat + lineDash = listOf(8.0, 8.0) + } + } } } } diff --git a/livemap-demo/src/commonMain/kotlin/jetbrains/livemap/model/Extensions.kt b/livemap-demo/src/commonMain/kotlin/jetbrains/livemap/model/Extensions.kt index 3e128bdbc08..b1380eece9c 100644 --- a/livemap-demo/src/commonMain/kotlin/jetbrains/livemap/model/Extensions.kt +++ b/livemap-demo/src/commonMain/kotlin/jetbrains/livemap/model/Extensions.kt @@ -1,10 +1,7 @@ package jetbrains.livemap.demo import jetbrains.datalore.base.values.Color -import jetbrains.livemap.api.PathBuilder -import jetbrains.livemap.api.Paths -import jetbrains.livemap.api.PointBuilder -import jetbrains.livemap.api.Points +import jetbrains.livemap.api.* import jetbrains.livemap.demo.model.GeoObject fun Points.point(block: PointBuilder.() -> Unit) { @@ -51,6 +48,41 @@ fun Paths.path(block: PathBuilder.() -> Unit) { ) } +fun Polygons.polygon(block: PolygonsBuilder.() -> Unit) { + items.add( + PolygonsBuilder().apply { + index = 0 + mapId = "" + regionId = "" + + lineDash = emptyList() + strokeColor = Color.BLACK + strokeWidth = 0.0 + fillColor = Color.GREEN + coordinates = emptyList() + } + .apply(block) + .build() + ) +} + +fun Lines.line(block: LineBuilder.() -> Unit) { + items.add( + LineBuilder().apply { + index = 0 + mapId = "" + regionId = "" + + lineDash = emptyList() + strokeColor = Color.BLACK + strokeWidth = 1.0 + + } + .apply(block) + .build() + ) +} + fun PointBuilder.coord(geoObj: GeoObject) { lon = geoObj.lon lat = geoObj.lat diff --git a/livemap/src/commonMain/kotlin/jetbrains/livemap/LiveMap.kt b/livemap/src/commonMain/kotlin/jetbrains/livemap/LiveMap.kt index 1e6a390b45a..e016b04b76d 100644 --- a/livemap/src/commonMain/kotlin/jetbrains/livemap/LiveMap.kt +++ b/livemap/src/commonMain/kotlin/jetbrains/livemap/LiveMap.kt @@ -273,11 +273,11 @@ class LiveMap( when(kind) { MapLayerKind.POINT -> mapObject2Entity.processPoint(mapObjects) MapLayerKind.PATH -> mapObject2Entity.processPath(mapObjects) - //MapLayerKind.POLYGON -> mapObject2Entity.processPolygon(mapObjects) + MapLayerKind.POLYGON -> mapObject2Entity.processPolygon(mapObjects) //MapLayerKind.BAR -> mapObject2Entity.processBar(mapObjects) //MapLayerKind.PIE -> mapObject2Entity.processPie(mapObjects) - //MapLayerKind.H_LINE -> mapObject2Entity.processLine(mapObjects, true, myMapProjection.mapRect) - //MapLayerKind.V_LINE -> mapObject2Entity.processLine(mapObjects, false, myMapProjection.mapRect) + MapLayerKind.H_LINE -> mapObject2Entity.processLine(mapObjects, true) + MapLayerKind.V_LINE -> mapObject2Entity.processLine(mapObjects, false) //MapLayerKind.TEXT -> mapObject2Entity.processText( //mapObjects, //TextSpec.createMeasurer(context.mapRenderContext.canvasProvider.createCanvas(Vector.ZERO).context2d) diff --git a/livemap/src/commonMain/kotlin/jetbrains/livemap/api/Builder.kt b/livemap/src/commonMain/kotlin/jetbrains/livemap/api/Builder.kt index 46d64ddc528..c6221853b31 100644 --- a/livemap/src/commonMain/kotlin/jetbrains/livemap/api/Builder.kt +++ b/livemap/src/commonMain/kotlin/jetbrains/livemap/api/Builder.kt @@ -23,9 +23,10 @@ import jetbrains.livemap.LiveMapSpec import jetbrains.livemap.MapLocation import jetbrains.livemap.entities.geometry.LonLatGeometry import jetbrains.livemap.mapobjects.MapLayer -import jetbrains.livemap.mapobjects.MapLayerKind.PATH -import jetbrains.livemap.mapobjects.MapLayerKind.POINT +import jetbrains.livemap.mapobjects.MapLayerKind.* +import jetbrains.livemap.mapobjects.MapLine import jetbrains.livemap.mapobjects.MapPoint +import jetbrains.livemap.mapobjects.MapPolygon import jetbrains.livemap.projections.ProjectionType import jetbrains.livemap.projections.createArcPath @@ -110,6 +111,16 @@ class Paths { val items = ArrayList() } +@LiveMapDsl +class Polygons { + val items = ArrayList() +} + +@LiveMapDsl +class Lines { + val items = ArrayList() +} + @LiveMapDsl class PointBuilder { var animation: Int? = null @@ -174,6 +185,58 @@ class PathBuilder { } } + + +@LiveMapDsl +class PolygonsBuilder { + var index: Int? = null + var mapId: String? = null + var regionId: String? = null + + var lineDash: List? = null + var strokeColor: Color? = null + var strokeWidth: Double? = null + var fillColor: Color? = null + var coordinates: List? = null + + fun build(): MapPolygon { + + return MapPolygon( + index!!, mapId, regionId, + lineDash!!, strokeColor!!, strokeWidth!!, + fillColor!!, + coordinates!! + .run(::Ring) + .run(::Polygon) + .run(::MultiPolygon) + .run(LonLatGeometry.Companion::create) + ) + } +} + +@LiveMapDsl +class LineBuilder { + var index: Int? = null + var mapId: String? = null + var regionId: String? = null + + var lon: Double? = null + var lat: Double? = null + var lineDash: List? = null + var strokeColor: Color? = null + var strokeWidth: Double? = null + + + fun build(): MapLine { + + return MapLine( + index!!, mapId, regionId, + lineDash!!, strokeColor!!, strokeWidth!!, + DoubleVector(lon!!, lat!!) + ) + } +} + @LiveMapDsl class Location { var name: String? = null @@ -228,6 +291,18 @@ fun LayersBuilder.paths(block: Paths.() -> Unit) { items.add(MapLayer(PATH, Paths().apply(block).items)) } +fun LayersBuilder.polygons(block: Polygons.() -> Unit) { + items.add(MapLayer(POLYGON, Polygons().apply(block).items)) +} + +fun LayersBuilder.hLines(block: Lines.() -> Unit) { + items.add(MapLayer(H_LINE, Lines().apply(block).items)) +} + +fun LayersBuilder.vLines(block: Lines.() -> Unit) { + items.add(MapLayer(V_LINE, Lines().apply(block).items)) +} + fun point(block: PointBuilder.() -> Unit) = PointBuilder().apply(block) fun path(block: PathBuilder.() -> Unit) = PathBuilder().apply(block) diff --git a/livemap/src/commonMain/kotlin/jetbrains/livemap/entities/rendering/StyleComponent.kt b/livemap/src/commonMain/kotlin/jetbrains/livemap/entities/rendering/StyleComponent.kt index 75fe7634b25..30a6e1cf842 100644 --- a/livemap/src/commonMain/kotlin/jetbrains/livemap/entities/rendering/StyleComponent.kt +++ b/livemap/src/commonMain/kotlin/jetbrains/livemap/entities/rendering/StyleComponent.kt @@ -13,3 +13,4 @@ class StyleComponent : EcsComponent { fun StyleComponent.setLineDash(lineDash: List) { this.lineDash = lineDash.toDoubleArray() } fun StyleComponent.setFillColor(fillColor: Color) { this.fillColor = fillColor.toCssColor() } fun StyleComponent.setStrokeColor(strokeColor: Color) { this.strokeColor = strokeColor.toCssColor() } +fun StyleComponent.setStrokeWidth(strokeWidth: Double) { this.strokeWidth = strokeWidth } diff --git a/livemap/src/commonMain/kotlin/jetbrains/livemap/mapobjects/MapLine.kt b/livemap/src/commonMain/kotlin/jetbrains/livemap/mapobjects/MapLine.kt new file mode 100644 index 00000000000..813f20e0c40 --- /dev/null +++ b/livemap/src/commonMain/kotlin/jetbrains/livemap/mapobjects/MapLine.kt @@ -0,0 +1,15 @@ +package jetbrains.livemap.mapobjects + +import jetbrains.datalore.base.geometry.DoubleVector +import jetbrains.datalore.base.values.Color + +class MapLine( + index: Int, + mapId: String?, + regionId: String?, + + val lineDash: List, + val strokeColor: Color, + val strokeWidth: Double, + val point: DoubleVector +) : MapObject(index, mapId, regionId) \ No newline at end of file diff --git a/livemap/src/commonMain/kotlin/jetbrains/livemap/mapobjects/MapPolygon.kt b/livemap/src/commonMain/kotlin/jetbrains/livemap/mapobjects/MapPolygon.kt new file mode 100644 index 00000000000..02b90d2b33f --- /dev/null +++ b/livemap/src/commonMain/kotlin/jetbrains/livemap/mapobjects/MapPolygon.kt @@ -0,0 +1,17 @@ +package jetbrains.livemap.mapobjects + +import jetbrains.datalore.base.values.Color +import jetbrains.livemap.entities.geometry.LonLatGeometry + +class MapPolygon( + index: Int, + mapId: String?, + regionId: String?, + + val lineDash: List, + val strokeColor: Color, + val strokeWidth: Double, + val fillColor: Color, + val geometry: LonLatGeometry? + +) : MapObject(index, mapId, regionId) \ No newline at end of file diff --git a/livemap/src/commonMain/kotlin/jetbrains/livemap/obj2entity/MapLineProcessor.kt b/livemap/src/commonMain/kotlin/jetbrains/livemap/obj2entity/MapLineProcessor.kt new file mode 100644 index 00000000000..454a8b08cbb --- /dev/null +++ b/livemap/src/commonMain/kotlin/jetbrains/livemap/obj2entity/MapLineProcessor.kt @@ -0,0 +1,105 @@ +package jetbrains.livemap.obj2entity + +import jetbrains.datalore.base.geometry.DoubleRectangle +import jetbrains.datalore.base.geometry.DoubleVector +import jetbrains.datalore.base.projectionGeometry.MultiPolygon +import jetbrains.datalore.base.projectionGeometry.Polygon +import jetbrains.datalore.base.projectionGeometry.Ring +import jetbrains.datalore.maps.livemap.entities.geometry.Renderers +import jetbrains.datalore.maps.livemap.entities.geometry.WorldGeometryComponent +import jetbrains.livemap.core.ecs.EcsComponentManager +import jetbrains.livemap.core.rendering.layers.LayerManager +import jetbrains.livemap.entities.Entities +import jetbrains.livemap.entities.geometry.WorldGeometry +import jetbrains.livemap.entities.placement.Components +import jetbrains.livemap.entities.rendering.* +import jetbrains.livemap.mapobjects.MapLine +import jetbrains.livemap.mapobjects.MapObject +import jetbrains.livemap.projections.MapProjection +import jetbrains.livemap.projections.WorldPoint +import jetbrains.livemap.projections.toWorldPoint + +class MapLineProcessor internal constructor( + componentManager: EcsComponentManager, + layerManager: LayerManager, + private val myMapProjection: MapProjection +) { + + private val myLayerEntitiesComponent = LayerEntitiesComponent() + private val myFactory: Entities.MapEntityFactory + + init { + val layerEntity = componentManager + .createEntity("map_layer_line") + .addComponent(layerManager.createRenderLayerComponent("geom_line")) + .addComponent(myLayerEntitiesComponent) + myFactory = Entities.MapEntityFactory(layerEntity) + } + + internal fun process(mapObjects: List, horizontal: Boolean) { + createEntities(mapObjects, horizontal) + } + + private fun createEntities(mapObjects: List, horizontal: Boolean) { + for (obj in mapObjects) { + val mapLine = obj as MapLine + val worldPoint = myMapProjection.project(mapLine.point).toWorldPoint() + val geometry = createLineGeometry(worldPoint, horizontal, myMapProjection.mapRect) + val bbox = createLineBBox(worldPoint, mapLine.strokeWidth, horizontal, myMapProjection.mapRect) + + val lineEntity = myFactory + .createMapEntity(bbox.origin.toWorldPoint(), SIMPLE_RENDERER, "map_ent_line") + .addComponent(WorldGeometryComponent().apply { this.geometry = geometry }) + .addComponent(Components.WorldDimensionComponent(bbox.dimension.toWorldPoint())) + .addComponent( + StyleComponent().apply { + setStrokeColor(mapLine.strokeColor) + setStrokeWidth(mapLine.strokeWidth) + setLineDash(mapLine.lineDash) + } + ) + + myLayerEntitiesComponent.add(lineEntity.id) + } + } + + companion object { + private val SIMPLE_RENDERER = Renderers.PathRenderer() + + private fun createLineGeometry(point: WorldPoint, horizontal: Boolean, mapRect: DoubleRectangle): WorldGeometry { + return if (horizontal) { + listOf( + DoubleVector(mapRect.left, point.y), + DoubleVector(mapRect.right, point.y) + ) + } else { + listOf( + DoubleVector(point.x, mapRect.top), + DoubleVector(point.x, mapRect.bottom) + ) + } + .run(::Ring) + .run(::Polygon) + .run(::MultiPolygon) + .run(WorldGeometry.Companion::create) + } + + private fun createLineBBox( + point: WorldPoint, + strokeWidth: Double, + horizontal: Boolean, + mapRect: DoubleRectangle + ): DoubleRectangle { + val origin: DoubleVector + val dimension: DoubleVector + if (horizontal) { + origin = DoubleVector(mapRect.left, point.y - strokeWidth / 2) + dimension = DoubleVector(mapRect.width, strokeWidth) + } else { + origin = DoubleVector(point.x - strokeWidth / 2, mapRect.top) + dimension = DoubleVector(strokeWidth, mapRect.height) + } + return DoubleRectangle(origin, dimension) + } + } +} \ No newline at end of file diff --git a/livemap/src/commonMain/kotlin/jetbrains/livemap/obj2entity/MapObject2Entity.kt b/livemap/src/commonMain/kotlin/jetbrains/livemap/obj2entity/MapObject2Entity.kt index 2fbcb3a0a01..1c8e1e9e775 100644 --- a/livemap/src/commonMain/kotlin/jetbrains/livemap/obj2entity/MapObject2Entity.kt +++ b/livemap/src/commonMain/kotlin/jetbrains/livemap/obj2entity/MapObject2Entity.kt @@ -22,11 +22,17 @@ class MapObject2Entity( MapPathProcessor(myComponentManager, myLayerManager, myMapProjection).process(mapObjects) } - /* + fun processLine(mapObjects: List, horizontal: Boolean) { + MapLineProcessor(myComponentManager, myLayerManager, myMapProjection).process(mapObjects, horizontal) + } + fun processPolygon(mapObjects: List) { - MapPolygonProcessor(myComponentManager, myLayerManager).process(mapObjects) + MapPolygonProcessor(myComponentManager, myLayerManager, myMapProjection).process(mapObjects) } + + /* + fun processBar(mapObjects: List) { MapBarProcessor(myComponentManager, myLayerManager).process(mapObjects) } @@ -35,10 +41,6 @@ class MapObject2Entity( MapPieProcessor(myComponentManager, myLayerManager).process(mapObjects) } - fun processLine(mapObjects: List, horizontal: Boolean, mapRect: DoubleRectangle) { - MapLineProcessor(myComponentManager, myLayerManager, mapRect).process(mapObjects, horizontal) - } - fun processText(mapObjects: List, textMeasurer: TextMeasurer) { MapTextProcessor(myComponentManager, myLayerManager, textMeasurer).process(mapObjects) } diff --git a/livemap/src/commonMain/kotlin/jetbrains/livemap/obj2entity/MapPolygonProcessor.kt b/livemap/src/commonMain/kotlin/jetbrains/livemap/obj2entity/MapPolygonProcessor.kt new file mode 100644 index 00000000000..3c5fd981a6b --- /dev/null +++ b/livemap/src/commonMain/kotlin/jetbrains/livemap/obj2entity/MapPolygonProcessor.kt @@ -0,0 +1,102 @@ +package jetbrains.livemap.obj2entity + +import jetbrains.datalore.maps.livemap.entities.geometry.Renderers +import jetbrains.datalore.maps.livemap.entities.geometry.WorldGeometryComponent +import jetbrains.gis.geoprotocol.GeometryUtil +import jetbrains.livemap.core.ecs.EcsComponentManager +import jetbrains.livemap.core.rendering.layers.LayerManager +import jetbrains.livemap.entities.Entities +import jetbrains.livemap.entities.geometry.LonLatGeometry +import jetbrains.livemap.entities.geometry.WorldGeometry +import jetbrains.livemap.entities.placement.Components +import jetbrains.livemap.entities.rendering.* +import jetbrains.livemap.entities.scaling.ScaleComponent +import jetbrains.livemap.mapobjects.MapObject +import jetbrains.livemap.mapobjects.MapPolygon +import jetbrains.livemap.projections.MapProjection +import jetbrains.livemap.projections.ProjectionUtil +import jetbrains.livemap.projections.toWorldPoint + +internal class MapPolygonProcessor( + componentManager: EcsComponentManager, + layerManager: LayerManager, + private val myMapProjection: MapProjection +) { + private val myFactory: Entities.MapEntityFactory + private val myLayerEntitiesComponent = LayerEntitiesComponent() + private val toMapProjection: (LonLatGeometry) -> WorldGeometry + + init { + myFactory = Entities.MapEntityFactory( + componentManager + .createEntity("map_layer_polygon") + .addComponent(layerManager.createRenderLayerComponent("geom_polygon")) + .addComponent(myLayerEntitiesComponent) + ) + + toMapProjection = { geometry -> + geometry.asMultipolygon() + .run { ProjectionUtil.transformMultipolygon(this, myMapProjection::project) } + .run { WorldGeometry.create(this) } + } + } + + fun process(mapObjects: List) { + createEntities(mapObjects) + } + + private fun createEntities(mapObjects: List) { + for (`object` in mapObjects) { + val mapPolygon = `object` as MapPolygon + + if (mapPolygon.geometry != null) { + createStaticEntity(mapPolygon) + } else if (mapPolygon.regionId != null) { +// createDynamicEntity(mapPolygon) + } else { + // do not create entities for empty geometries + } + } + } + + private fun createStaticEntity(mapPolygon: MapPolygon) { + val geometry = toMapProjection(mapPolygon.geometry!!) + val bbox = GeometryUtil.bbox(geometry.asMultipolygon()) ?: error("") + + val geometryEntity = myFactory + .createMapEntity(bbox.origin.toWorldPoint(), SIMPLE_RENDERER, "map_ent_spolygon") + .addComponent(WorldGeometryComponent().apply { this.geometry = geometry } ) + .addComponent(Components.WorldDimensionComponent(bbox.dimension.toWorldPoint())) + .addComponent(ScaleComponent()) + .addComponent( + StyleComponent().apply { + setFillColor(mapPolygon.fillColor) + setStrokeColor(mapPolygon.strokeColor) + setStrokeWidth(mapPolygon.strokeWidth) + } + ) + + myLayerEntitiesComponent.add(geometryEntity.id) + } + +// private fun createDynamicEntity(mapPolygon: MapPolygon) { +// val regionEntity = myFactory +// .createDynamicMapEntity("map_ent_dpolygon_" + mapPolygon.regionId, FRAGMENTS_RENDERER) +// .addComponent(RegionComponent().apply { id = mapPolygon.regionId }) +// .addComponent( +// StyleComponent().apply { +// setFillColor(mapPolygon.fillColor) +// setStrokeColor(mapPolygon.strokeColor) +// setStrokeWidth(mapPolygon.strokeWidth) +// } +// ) +// +// regionEntity.get().origins = listOf(ZERO_CLIENT_POINT) +// myLayerEntitiesComponent.add(regionEntity.id) +// } + + companion object { + private val SIMPLE_RENDERER = Renderers.PolygonRenderer() + // private val FRAGMENTS_RENDERER = RegionRenderer() + } +} \ No newline at end of file