mirror of https://github.com/Fabio286/antares.git
feat(MySQL): spatial fields support (#165)
* feat: POINT field support * feat(MySQL): support to LINESTRING, POLYGON and GEOMETRY fields * refactor: removed links from map attribution * feat(MySQL): support to MULTIPOINT, MULTILINESTRING, MULTIPOLYGON and GEOMCOLLECTION fields * test: temporary fix on Windows tests
This commit is contained in:
parent
64deedc5ad
commit
48ebf23bd1
|
@ -106,6 +106,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/remote": "^2.0.1",
|
"@electron/remote": "^2.0.1",
|
||||||
"@mdi/font": "^6.1.95",
|
"@mdi/font": "^6.1.95",
|
||||||
|
"@turf/helpers": "^6.5.0",
|
||||||
"@vscode/vscode-languagedetection": "^1.0.21",
|
"@vscode/vscode-languagedetection": "^1.0.21",
|
||||||
"ace-builds": "^1.4.13",
|
"ace-builds": "^1.4.13",
|
||||||
"better-sqlite3": "^7.4.4",
|
"better-sqlite3": "^7.4.4",
|
||||||
|
@ -113,7 +114,8 @@
|
||||||
"electron-store": "^8.0.1",
|
"electron-store": "^8.0.1",
|
||||||
"electron-updater": "^4.6.1",
|
"electron-updater": "^4.6.1",
|
||||||
"electron-window-state": "^5.0.3",
|
"electron-window-state": "^5.0.3",
|
||||||
"faker": "~5.5.3",
|
"faker": "^5.5.3",
|
||||||
|
"leaflet": "^1.7.1",
|
||||||
"marked": "^4.0.0",
|
"marked": "^4.0.0",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"mysql2": "^2.3.2",
|
"mysql2": "^2.3.2",
|
||||||
|
|
|
@ -219,56 +219,56 @@ module.exports = [
|
||||||
types: [
|
types: [
|
||||||
{
|
{
|
||||||
name: 'POINT',
|
name: 'POINT',
|
||||||
length: true,
|
length: false,
|
||||||
collation: false,
|
collation: false,
|
||||||
unsigned: false,
|
unsigned: false,
|
||||||
zerofill: false
|
zerofill: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'LINESTRING',
|
name: 'LINESTRING',
|
||||||
length: true,
|
length: false,
|
||||||
collation: false,
|
collation: false,
|
||||||
unsigned: false,
|
unsigned: false,
|
||||||
zerofill: false
|
zerofill: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'POLYGON',
|
name: 'POLYGON',
|
||||||
length: true,
|
length: false,
|
||||||
collation: false,
|
collation: false,
|
||||||
unsigned: false,
|
unsigned: false,
|
||||||
zerofill: false
|
zerofill: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'GEOMETRY',
|
name: 'GEOMETRY',
|
||||||
length: true,
|
length: false,
|
||||||
collation: false,
|
collation: false,
|
||||||
unsigned: false,
|
unsigned: false,
|
||||||
zerofill: false
|
zerofill: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'MULTIPOINT',
|
name: 'MULTIPOINT',
|
||||||
length: true,
|
length: false,
|
||||||
collation: false,
|
collation: false,
|
||||||
unsigned: false,
|
unsigned: false,
|
||||||
zerofill: false
|
zerofill: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'MULTILINESTRING',
|
name: 'MULTILINESTRING',
|
||||||
length: true,
|
length: false,
|
||||||
collation: false,
|
collation: false,
|
||||||
unsigned: false,
|
unsigned: false,
|
||||||
zerofill: false
|
zerofill: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'MULTIPOLYGON',
|
name: 'MULTIPOLYGON',
|
||||||
length: true,
|
length: false,
|
||||||
collation: false,
|
collation: false,
|
||||||
unsigned: false,
|
unsigned: false,
|
||||||
zerofill: false
|
zerofill: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'GEOMETRYCOLLECTION',
|
name: 'GEOMCOLLECTION',
|
||||||
length: true,
|
length: false,
|
||||||
collation: false,
|
collation: false,
|
||||||
unsigned: false,
|
unsigned: false,
|
||||||
zerofill: false
|
zerofill: false
|
||||||
|
|
|
@ -8,7 +8,9 @@ export const TEXT = [
|
||||||
export const LONG_TEXT = [
|
export const LONG_TEXT = [
|
||||||
'TEXT',
|
'TEXT',
|
||||||
'MEDIUMTEXT',
|
'MEDIUMTEXT',
|
||||||
'LONGTEXT'
|
'LONGTEXT',
|
||||||
|
'JSON',
|
||||||
|
'VARBINARY'
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ARRAY = [
|
export const ARRAY = [
|
||||||
|
@ -82,3 +84,24 @@ export const BIT = [
|
||||||
'BIT',
|
'BIT',
|
||||||
'BIT VARYING'
|
'BIT VARYING'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const SPATIAL = [
|
||||||
|
'POINT',
|
||||||
|
'LINESTRING',
|
||||||
|
'POLYGON',
|
||||||
|
'GEOMETRY',
|
||||||
|
'MULTIPOINT',
|
||||||
|
'MULTILINESTRING',
|
||||||
|
'MULTIPOLYGON',
|
||||||
|
'GEOMCOLLECTION',
|
||||||
|
'GEOMETRYCOLLECTION'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Used to check multi spatial fields only
|
||||||
|
export const IS_MULTI_SPATIAL = [
|
||||||
|
'MULTIPOINT',
|
||||||
|
'MULTILINESTRING',
|
||||||
|
'MULTIPOLYGON',
|
||||||
|
'GEOMCOLLECTION',
|
||||||
|
'GEOMETRYCOLLECTION'
|
||||||
|
];
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {any[]} array
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
export function getArrayDepth (array) {
|
||||||
|
return Array.isArray(array)
|
||||||
|
? 1 + Math.max(0, ...array.map(getArrayDepth))
|
||||||
|
: 0;
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
<template>
|
||||||
|
<div id="map" class="map" />
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import L from 'leaflet';
|
||||||
|
import {
|
||||||
|
point,
|
||||||
|
lineString,
|
||||||
|
polygon
|
||||||
|
} from '@turf/helpers';
|
||||||
|
import { getArrayDepth } from 'common/libs/getArrayDepth';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'BaseMap',
|
||||||
|
props: {
|
||||||
|
points: [Object, Array],
|
||||||
|
isMultiSpatial: Boolean
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
map: null,
|
||||||
|
markers: [],
|
||||||
|
center: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
if (this.isMultiSpatial) {
|
||||||
|
for (const element of this.points)
|
||||||
|
this.markers.push(this.getMarkers(element));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.markers = this.getMarkers(this.points);
|
||||||
|
|
||||||
|
if (!Array.isArray(this.points))
|
||||||
|
this.center = [this.points.y, this.points.x];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.map = L.map('map', {
|
||||||
|
center: this.center || [0, 0],
|
||||||
|
zoom: 15,
|
||||||
|
minZoom: 1,
|
||||||
|
attributionControl: false
|
||||||
|
});
|
||||||
|
|
||||||
|
L.control.attribution({ prefix: '<b>Leaflet</b>' }).addTo(this.map);
|
||||||
|
|
||||||
|
const geoJsonObj = L.geoJSON(this.markers, {
|
||||||
|
style: function () {
|
||||||
|
return {
|
||||||
|
weight: 2,
|
||||||
|
fillColor: '#ff7800',
|
||||||
|
color: '#ff7800',
|
||||||
|
opacity: 0.8,
|
||||||
|
fillOpacity: 0.4
|
||||||
|
};
|
||||||
|
},
|
||||||
|
pointToLayer: function (feature, latlng) {
|
||||||
|
return L.circleMarker(latlng, {
|
||||||
|
radius: 7,
|
||||||
|
weight: 2,
|
||||||
|
fillColor: '#ff7800',
|
||||||
|
color: '#ff7800',
|
||||||
|
opacity: 0.8,
|
||||||
|
fillOpacity: 0.4
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).addTo(this.map);
|
||||||
|
|
||||||
|
const southWest = L.latLng(-90, -180);
|
||||||
|
const northEast = L.latLng(90, 180);
|
||||||
|
const bounds = L.latLngBounds(southWest, northEast);
|
||||||
|
this.map.setMaxBounds(bounds);
|
||||||
|
|
||||||
|
if (!this.center) this.map.fitBounds(geoJsonObj.getBounds());
|
||||||
|
|
||||||
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
attribution: '© <b>OpenStreetMap</b>'
|
||||||
|
}).addTo(this.map);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getMarkers (points) {
|
||||||
|
if (Array.isArray(points)) {
|
||||||
|
if (getArrayDepth(points) === 1)
|
||||||
|
return lineString(points.reduce((acc, curr) => [...acc, [curr.x, curr.y]], []));
|
||||||
|
else
|
||||||
|
return polygon(points.map(arr => arr.reduce((acc, curr) => [...acc, [curr.x, curr.y]], [])));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return point([points.x, points.y]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.map{
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker-icon{
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: $primary-color;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 0 5px 1px darken($body-font-color-dark, 40%);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -412,7 +412,9 @@ export default {
|
||||||
`${this.fields[0].table}.${this.selectedCell.field}`,
|
`${this.fields[0].table}.${this.selectedCell.field}`,
|
||||||
`${this.fields[0].tableAlias}.${this.selectedCell.field}`
|
`${this.fields[0].tableAlias}.${this.selectedCell.field}`
|
||||||
].includes(prop));
|
].includes(prop));
|
||||||
const valueToCopy = row[cellName];
|
let valueToCopy = row[cellName];
|
||||||
|
if (typeof valueToCopy === 'object')
|
||||||
|
valueToCopy = JSON.stringify(valueToCopy);
|
||||||
navigator.clipboard.writeText(valueToCopy);
|
navigator.clipboard.writeText(valueToCopy);
|
||||||
},
|
},
|
||||||
copyRow () {
|
copyRow () {
|
||||||
|
|
|
@ -123,6 +123,21 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
|
<ConfirmModal
|
||||||
|
v-if="isMapModal"
|
||||||
|
:hide-footer="true"
|
||||||
|
size="medium"
|
||||||
|
@hide="hideEditorModal"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="mdi mdi-24px mdi-map mr-1" /> <span class="cut-text">"{{ editingField }}"</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #body>
|
||||||
|
<BaseMap :points="editingContent" :is-multi-spatial="isMultiSpatial" />
|
||||||
|
</template>
|
||||||
|
</ConfirmModal>
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
v-if="isBlobEditor"
|
v-if="isBlobEditor"
|
||||||
:confirm-text="$t('word.update')"
|
:confirm-text="$t('word.update')"
|
||||||
|
@ -187,10 +202,27 @@ import { mimeFromHex } from 'common/libs/mimeFromHex';
|
||||||
import { formatBytes } from 'common/libs/formatBytes';
|
import { formatBytes } from 'common/libs/formatBytes';
|
||||||
import { bufferToBase64 } from 'common/libs/bufferToBase64';
|
import { bufferToBase64 } from 'common/libs/bufferToBase64';
|
||||||
import hexToBinary from 'common/libs/hexToBinary';
|
import hexToBinary from 'common/libs/hexToBinary';
|
||||||
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BOOLEAN, DATE, TIME, DATETIME, BLOB, BIT, HAS_TIMEZONE } from 'common/fieldTypes';
|
import {
|
||||||
|
TEXT,
|
||||||
|
LONG_TEXT,
|
||||||
|
ARRAY,
|
||||||
|
TEXT_SEARCH,
|
||||||
|
NUMBER,
|
||||||
|
FLOAT,
|
||||||
|
BOOLEAN,
|
||||||
|
DATE,
|
||||||
|
TIME,
|
||||||
|
DATETIME,
|
||||||
|
BLOB,
|
||||||
|
BIT,
|
||||||
|
HAS_TIMEZONE,
|
||||||
|
SPATIAL,
|
||||||
|
IS_MULTI_SPATIAL
|
||||||
|
} from 'common/fieldTypes';
|
||||||
import { VueMaskDirective } from 'v-mask';
|
import { VueMaskDirective } from 'v-mask';
|
||||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||||
import TextEditor from '@/components/BaseTextEditor';
|
import TextEditor from '@/components/BaseTextEditor';
|
||||||
|
import BaseMap from '@/components/BaseMap';
|
||||||
import ForeignKeySelect from '@/components/ForeignKeySelect';
|
import ForeignKeySelect from '@/components/ForeignKeySelect';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -198,7 +230,8 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
ConfirmModal,
|
ConfirmModal,
|
||||||
TextEditor,
|
TextEditor,
|
||||||
ForeignKeySelect
|
ForeignKeySelect,
|
||||||
|
BaseMap
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
mask: VueMaskDirective
|
mask: VueMaskDirective
|
||||||
|
@ -248,7 +281,10 @@ export default {
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
return val;
|
if (SPATIAL.includes(type))
|
||||||
|
return val;
|
||||||
|
|
||||||
|
return typeof val === 'object' ? JSON.stringify(val) : val;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
@ -263,6 +299,8 @@ export default {
|
||||||
isInlineEditor: {},
|
isInlineEditor: {},
|
||||||
isTextareaEditor: false,
|
isTextareaEditor: false,
|
||||||
isBlobEditor: false,
|
isBlobEditor: false,
|
||||||
|
isMapModal: false,
|
||||||
|
isMultiSpatial: false,
|
||||||
willBeDeleted: false,
|
willBeDeleted: false,
|
||||||
originalContent: null,
|
originalContent: null,
|
||||||
editingContent: null,
|
editingContent: null,
|
||||||
|
@ -331,6 +369,9 @@ export default {
|
||||||
if (BOOLEAN.includes(this.editingType))
|
if (BOOLEAN.includes(this.editingType))
|
||||||
return { type: 'boolean', mask: false };
|
return { type: 'boolean', mask: false };
|
||||||
|
|
||||||
|
if (SPATIAL.includes(this.editingType))
|
||||||
|
return { type: 'map', mask: false };
|
||||||
|
|
||||||
return { type: 'text', mask: false };
|
return { type: 'text', mask: false };
|
||||||
},
|
},
|
||||||
isImage () {
|
isImage () {
|
||||||
|
@ -403,7 +444,7 @@ export default {
|
||||||
return bufferToBase64(val);
|
return bufferToBase64(val);
|
||||||
},
|
},
|
||||||
editON (event, content, field) {
|
editON (event, content, field) {
|
||||||
if (!this.isEditable) return;
|
if (!this.isEditable || this.editingType === 'none') return;
|
||||||
|
|
||||||
window.addEventListener('keydown', this.onKey);
|
window.addEventListener('keydown', this.onKey);
|
||||||
|
|
||||||
|
@ -419,6 +460,15 @@ export default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SPATIAL.includes(type)) {
|
||||||
|
if (content) {
|
||||||
|
this.isMultiSpatial = IS_MULTI_SPATIAL.includes(type);
|
||||||
|
this.isMapModal = true;
|
||||||
|
this.editingContent = this.$options.filters.typeFormat(content, type);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (BLOB.includes(type)) {
|
if (BLOB.includes(type)) {
|
||||||
this.isBlobEditor = true;
|
this.isBlobEditor = true;
|
||||||
this.editingContent = content || '';
|
this.editingContent = content || '';
|
||||||
|
@ -490,6 +540,8 @@ export default {
|
||||||
hideEditorModal () {
|
hideEditorModal () {
|
||||||
this.isTextareaEditor = false;
|
this.isTextareaEditor = false;
|
||||||
this.isBlobEditor = false;
|
this.isBlobEditor = false;
|
||||||
|
this.isMapModal = false;
|
||||||
|
this.isMultiSpatial = false;
|
||||||
},
|
},
|
||||||
downloadFile () {
|
downloadFile () {
|
||||||
const downloadLink = document.createElement('a');
|
const downloadLink = document.createElement('a');
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
import '@mdi/font/css/materialdesignicons.css';
|
import '@mdi/font/css/materialdesignicons.css';
|
||||||
|
import 'leaflet/dist/leaflet.css';
|
||||||
import '@/scss/main.scss';
|
import '@/scss/main.scss';
|
||||||
|
|
||||||
import App from '@/App.vue';
|
import App from '@/App.vue';
|
||||||
|
|
|
@ -81,6 +81,15 @@
|
||||||
"tsvector": $array-color,
|
"tsvector": $array-color,
|
||||||
"tsquery": $array-color,
|
"tsquery": $array-color,
|
||||||
"pg_node_tree": $array-color,
|
"pg_node_tree": $array-color,
|
||||||
|
"point": $array-color,
|
||||||
|
"linestring": $array-color,
|
||||||
|
"polygon": $array-color,
|
||||||
|
"geometry": $array-color,
|
||||||
|
"multipoint": $array-color,
|
||||||
|
"multilinestring": $array-color,
|
||||||
|
"multipolygon": $array-color,
|
||||||
|
"geomcollection": $array-color,
|
||||||
|
"geometrycollection": $array-color,
|
||||||
"aclitem": $array-color,
|
"aclitem": $array-color,
|
||||||
"unknown": $unknown-color,
|
"unknown": $unknown-color,
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue