Skip to content

Commit 119694f

Browse files
Merge pull request #20192 from MoonE/ol-wkt
Transmit OpenLayers data as WKT
2 parents 0c77c26 + ecf75a9 commit 119694f

22 files changed

Lines changed: 896 additions & 2257 deletions

resources/js/gis_data_editor.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import $ from 'jquery';
22
import { AJAX } from './modules/ajax.ts';
33
import { CommonParams } from './modules/common.ts';
44
import { ajaxShowMessage } from './modules/ajax-message.ts';
5+
import { OlData } from './table/gis_visualization.ts';
56

67
/**
78
* @fileoverview functions used in GIS data editor
@@ -23,7 +24,7 @@ function disposeGISEditorVisualization () {
2324
/**
2425
* Initialize the visualization in the GIS data editor.
2526
*/
26-
function initGISEditorVisualization (olData: any[]) {
27+
function initGISEditorVisualization (olData: OlData) {
2728
visualizationController = new window.GisVisualizationController(olData);
2829
}
2930

resources/js/table/gis_visualization.ts

Lines changed: 142 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
11
import $ from 'jquery';
22
import { AJAX } from '../modules/ajax.ts';
33
import { escapeHtml } from '../modules/functions/escape.ts';
4-
import { Feature, Map, View } from 'ol';
5-
import { Attribution, MousePosition, Zoom } from 'ol/control';
4+
import Feature from 'ol/Feature';
5+
import Map from 'ol/Map';
6+
import View from 'ol/View';
7+
import Attribution from 'ol/control/Attribution';
8+
import MousePosition from 'ol/control/MousePosition';
9+
import Zoom from 'ol/control/Zoom';
610
import { createStringXY } from 'ol/coordinate';
711
import { isEmpty } from 'ol/extent';
8-
import { LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon } from 'ol/geom';
9-
import { Tile, Vector as VectorLayer } from 'ol/layer';
12+
import WKT from 'ol/format/WKT';
13+
import Geometry from 'ol/geom/Geometry';
14+
import GeometryCollection from 'ol/geom/GeometryCollection';
15+
import Tile from 'ol/layer/Tile';
16+
import VectorLayer from 'ol/layer/Vector';
17+
import OSM from 'ol/source/OSM';
18+
import VectorSource from 'ol/source/Vector';
19+
import Circle from 'ol/style/Circle';
20+
import Fill from 'ol/style/Fill';
21+
import Stroke from 'ol/style/Stroke';
22+
import Style from 'ol/style/Style';
23+
import Text from 'ol/style/Text';
1024
import { get as getProjection } from 'ol/proj';
11-
import { OSM, Vector as VectorSource } from 'ol/source';
12-
import { Circle, Fill, Stroke, Style, Text } from 'ol/style';
1325

1426
/**
1527
* @fileoverview functions used for visualizing GIS data
@@ -422,20 +434,72 @@ class SvgVisualization extends GisVisualization {
422434
}
423435
}
424436

437+
interface OlGeometryData {
438+
wkt: string;
439+
srid?: number;
440+
label?: string;
441+
}
442+
443+
export interface OlData {
444+
geometries: OlGeometryData[];
445+
colors: [number, number, number][];
446+
}
447+
425448
class OlVisualization extends GisVisualization {
426449
private olMap: any = undefined;
427450

428-
private data: any[];
451+
private data: OlData;
429452

430-
/**
431-
* @param {function(HTMLElement): ol.Map} initFn
432-
*/
433-
constructor (target: HTMLElement, data: any[]) {
453+
constructor (target: HTMLElement, data: OlData) {
434454
super(target);
435455

436456
this.data = data;
437457
}
438458

459+
getFeaturesFromData (geometries: OlGeometryData[]): Feature[] {
460+
let features: Feature[] = [];
461+
const addFeature = (index: number, data: OlGeometryData, geometry: Geometry) => {
462+
const feature = new Feature(geometry);
463+
feature.set('index', index, true);
464+
if (data.label) {
465+
feature.set('label', data.label, true);
466+
}
467+
468+
features.push(feature);
469+
};
470+
471+
const projections = {};
472+
const reader = new WKT();
473+
for (let i = 0, ii = geometries.length; i < ii; ++i) {
474+
const data = geometries[i];
475+
const dataProjection = 'EPSG:' + (data.srid ?? 4326);
476+
if (!(dataProjection in projections)) {
477+
projections[dataProjection] = getProjection(dataProjection);
478+
}
479+
480+
// Skip features which use an unknown projection
481+
if (!projections[dataProjection]) {
482+
continue;
483+
}
484+
485+
const geometry = reader.readGeometry(data.wkt, {
486+
dataProjection,
487+
featureProjection: 'EPSG:3857',
488+
});
489+
490+
if (geometry.getType() === 'GeometryCollection') {
491+
const geometries = (geometry as GeometryCollection).getGeometriesArrayRecursive();
492+
geometries.forEach((geometry) => {
493+
addFeature(i, data, geometry);
494+
});
495+
} else {
496+
addFeature(i, data, geometry);
497+
}
498+
}
499+
500+
return features;
501+
}
502+
439503
drawOpenLayers () {
440504
if (! document.querySelector('script[src*="js/vendor/openlayers/openlayers.js"]')) {
441505
return undefined;
@@ -450,25 +514,86 @@ class OlVisualization extends GisVisualization {
450514
document.head.appendChild(link);
451515
}
452516

517+
const styleFactory = {
518+
Point: (color: [number, number, number]) => new Style({
519+
image: new Circle({
520+
radius: 3,
521+
fill: new Fill({ color: 'white' }),
522+
stroke: new Stroke({ width: 2, color }),
523+
}),
524+
text: new Text({
525+
stroke: new Stroke({ width: 2, color: 'white' }),
526+
}),
527+
}),
528+
LineString: (color: [number, number, number]) => new Style({
529+
stroke: new Stroke({ width: 2, color: color }),
530+
text: new Text({
531+
stroke: new Stroke({ width: 2, color: 'white' }),
532+
}),
533+
}),
534+
Polygon: (color: [number, number, number]) => new Style({
535+
fill: new Fill({ color: [...color, 0.8] }),
536+
stroke: new Stroke({ width: .5, color: 'black' }),
537+
text: new Text({
538+
stroke: new Stroke({ width: 2, color: 'white' }),
539+
}),
540+
}),
541+
};
542+
453543
const vectorSource = new VectorSource({
454-
features: getFeaturesFromOpenLayersData(this.data),
544+
features: this.getFeaturesFromData(this.data.geometries),
545+
});
546+
547+
let currentFeatureIndex: number|undefined;
548+
const colors = this.data.colors;
549+
const cache: Record<string, Style> = {};
550+
const vectorLayer = new VectorLayer({
551+
source: vectorSource,
552+
style: function (feature) {
553+
const index = feature.get('index');
554+
const type = feature.getGeometry().getType().replace('Multi', '');
555+
const selected = index === currentFeatureIndex;
556+
const key = type + (selected ? '+' : '-') + (index % colors.length);
557+
558+
let style = cache[key];
559+
if (!style) {
560+
let color = colors[index % colors.length];
561+
if (selected) {
562+
color = color.map((c) => c * 1.2) as [number, number, number];
563+
}
564+
565+
style = styleFactory[type](color);
566+
cache[key] = style;
567+
}
568+
569+
style.getText().setText(feature.get('label'));
570+
571+
return style;
572+
},
455573
});
456574
const map = new Map({
457575
target: this.target,
458576
layers: [
459577
new Tile({ source: new OSM() }),
460-
new VectorLayer({ source: vectorSource }),
578+
vectorLayer,
461579
],
462580
view: new View({ center: [0, 0], zoom: 4 }),
463581
controls: [
464582
new MousePosition({
465583
coordinateFormat: createStringXY(4),
466584
projection: 'EPSG:4326'
467585
}),
468-
new Zoom,
469-
new Attribution
586+
new Zoom(),
587+
new Attribution(),
470588
]
471589
});
590+
map.on('pointermove', (evt) => {
591+
const newFeatureIndex = (map.forEachFeatureAtPixel(evt.pixel, (f) => f))?.get('index') as number;
592+
if (newFeatureIndex !== currentFeatureIndex) {
593+
currentFeatureIndex = newFeatureIndex;
594+
vectorLayer.changed();
595+
}
596+
});
472597

473598
const extent = vectorSource.getExtent();
474599
if (! isEmpty(extent)) {
@@ -499,83 +624,16 @@ class OlVisualization extends GisVisualization {
499624
}
500625
}
501626

502-
function getFeaturesFromOpenLayersData (geometries: any[]): any[] {
503-
let features = [];
504-
for (const geometry of geometries) {
505-
if (geometry.isCollection) {
506-
features = features.concat(getFeaturesFromOpenLayersData(geometry.geometries));
507-
508-
continue;
509-
}
510-
511-
let olGeometry: any = null;
512-
const style: any = {};
513-
if (geometry.geometry.type === 'LineString') {
514-
olGeometry = new LineString(geometry.geometry.coordinates);
515-
style.stroke = new Stroke(geometry.style.stroke);
516-
} else if (geometry.geometry.type === 'MultiLineString') {
517-
olGeometry = new MultiLineString(geometry.geometry.coordinates);
518-
style.stroke = new Stroke(geometry.style.stroke);
519-
} else if (geometry.geometry.type === 'MultiPoint') {
520-
olGeometry = new MultiPoint(geometry.geometry.coordinates);
521-
style.image = new Circle({
522-
fill: new Fill(geometry.style.circle.fill),
523-
stroke: new Stroke(geometry.style.circle.stroke),
524-
radius: geometry.style.circle.radius,
525-
});
526-
} else if (geometry.geometry.type === 'MultiPolygon') {
527-
olGeometry = new MultiPolygon(geometry.geometry.coordinates);
528-
style.fill = new Fill(geometry.style.fill);
529-
style.stroke = new Stroke(geometry.style.stroke);
530-
} else if (geometry.geometry.type === 'Point') {
531-
olGeometry = new Point(geometry.geometry.coordinates);
532-
style.image = new Circle({
533-
fill: new Fill(geometry.style.circle.fill),
534-
stroke: new Stroke(geometry.style.circle.stroke),
535-
radius: geometry.style.circle.radius,
536-
});
537-
} else if (geometry.geometry.type === 'Polygon') {
538-
olGeometry = new Polygon(geometry.geometry.coordinates);
539-
style.fill = new Fill(geometry.style.fill);
540-
style.stroke = new Stroke(geometry.style.stroke);
541-
} else {
542-
throw new Error();
543-
}
544-
545-
if (geometry.geometry.srid !== 3857) {
546-
const source = 'EPSG:' + (geometry.geometry.srid !== 0 ? geometry.geometry.srid : 4326);
547-
const sourceProj = getProjection(source);
548-
549-
if (sourceProj) {
550-
olGeometry = olGeometry.transform(
551-
source,
552-
'EPSG:3857'
553-
);
554-
}
555-
}
556-
557-
if (geometry.style.text) {
558-
style.text = new Text(geometry.style.text);
559-
}
560-
561-
const feature = new Feature(olGeometry);
562-
feature.setStyle(new Style(style));
563-
features.push(feature);
564-
}
565-
566-
return features;
567-
}
568-
569627
class GisVisualizationController {
570628
private svgVis: SvgVisualization|undefined = undefined;
571629

572630
private olVis: OlVisualization|undefined = undefined;
573631

574632
private boundOnChoiceChange: any;
575633

576-
private olData: any[];
634+
private olData: OlData;
577635

578-
constructor (olData: any[]) {
636+
constructor (olData: OlData) {
579637
this.boundOnChoiceChange = this.onChoiceChange.bind(this);
580638
this.olData = olData;
581639

src/Gis/GisGeometry.php

Lines changed: 14 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
use PhpMyAdmin\Image\ImageWrapper;
1313
use TCPDF;
1414

15-
use function array_map;
1615
use function explode;
1716
use function mb_strripos;
1817
use function mb_substr;
@@ -82,16 +81,22 @@ abstract public function prepareRowAsPdf(
8281
* @param string $spatial GIS data object
8382
* @param int $srid spatial reference ID
8483
* @param string $label label for the GIS data object
85-
* @param int[] $color color for the GIS data object
8684
*
87-
* @return mixed[]
85+
* @psalm-return array{wkt: string, srid?: int, label?: non-empty-string}
8886
*/
89-
abstract public function prepareRowAsOl(
90-
string $spatial,
91-
int $srid,
92-
string $label,
93-
array $color,
94-
): array;
87+
public function prepareRowAsOl(string $spatial, int $srid, string $label): array
88+
{
89+
$data = ['wkt' => $spatial];
90+
if ($srid !== 0) {
91+
$data['srid'] = $srid;
92+
}
93+
94+
if ($label !== '') {
95+
$data['label'] = $label;
96+
}
97+
98+
return $data;
99+
}
95100

96101
/**
97102
* Get coordinate extent for this wkt.
@@ -294,30 +299,4 @@ protected function extractPoints1dLinear(string $wktCoords, ScaleData|null $scal
294299
{
295300
return $this->extractPointsInternal($wktCoords, $scaleData, true);
296301
}
297-
298-
/**
299-
* @param string $wktCoords string of ),( separated points
300-
* @param ScaleData|null $scaleData data related to scaling
301-
*
302-
* @return float[][][] scaled points
303-
*/
304-
protected function extractPoints2d(string $wktCoords, ScaleData|null $scaleData): array
305-
{
306-
$parts = explode('),(', $wktCoords);
307-
308-
return array_map(fn (string $coord): array => $this->extractPoints1d($coord, $scaleData), $parts);
309-
}
310-
311-
/**
312-
* @param string $wktCoords string of )),(( separated points
313-
* @param ScaleData|null $scaleData data related to scaling
314-
*
315-
* @return float[][][][] scaled points
316-
*/
317-
protected function extractPoints3d(string $wktCoords, ScaleData|null $scaleData): array
318-
{
319-
$parts = explode(')),((', $wktCoords);
320-
321-
return array_map(fn (string $coord): array => $this->extractPoints2d($coord, $scaleData), $parts);
322-
}
323302
}

0 commit comments

Comments
 (0)