mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-05-01 12:03:58 +00:00
map on standalone tab
This commit is contained in:
@@ -1,152 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import _ from 'lodash';
|
import MapView from '../elements/MapView.svelte';
|
||||||
import { onMount, tick } from 'svelte';
|
|
||||||
import 'leaflet/dist/leaflet.css';
|
|
||||||
import leaflet from 'leaflet';
|
|
||||||
import wellknown from 'wellknown';
|
|
||||||
import { isWktGeometry } from 'dbgate-tools';
|
|
||||||
import resizeObserver from '../utility/resizeObserver';
|
|
||||||
|
|
||||||
// import Map from 'ol/Map';
|
|
||||||
// import View from 'ol/View';
|
|
||||||
// import TileLayer from 'ol/layer/Tile';
|
|
||||||
// import XYZ from 'ol/source/XYZ';
|
|
||||||
|
|
||||||
export let selection;
|
export let selection;
|
||||||
export let wrap;
|
|
||||||
|
|
||||||
let refContainer;
|
|
||||||
let map;
|
|
||||||
|
|
||||||
let selectionLayers = [];
|
|
||||||
|
|
||||||
function createColumnsTable(cells) {
|
|
||||||
return `<table>${cells.map(cell => `<tr><td>${cell.column}</td><td>${cell.value}</td></tr>`).join('\n')}</table>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addSelectionToMap() {
|
|
||||||
if (!map) return;
|
|
||||||
if (!selection) return;
|
|
||||||
|
|
||||||
for (const selectionLayer of selectionLayers) {
|
|
||||||
selectionLayer.remove();
|
|
||||||
}
|
|
||||||
selectionLayers = [];
|
|
||||||
|
|
||||||
const selectedRows = _.groupBy(selection || [], 'row');
|
|
||||||
|
|
||||||
const features = [];
|
|
||||||
|
|
||||||
for (const rowKey of _.keys(selectedRows)) {
|
|
||||||
const cells = selectedRows[rowKey];
|
|
||||||
const lat = cells.find(x => x.column.toLowerCase().includes('lat'));
|
|
||||||
const lon = cells.find(x => x.column.toLowerCase().includes('lon') || x.column.toLowerCase().includes('lng'));
|
|
||||||
|
|
||||||
const geoValues = cells.map(x => x.value).filter(isWktGeometry);
|
|
||||||
|
|
||||||
if (lat && lon) {
|
|
||||||
features.push({
|
|
||||||
type: 'Feature',
|
|
||||||
properties: {
|
|
||||||
popupContent: createColumnsTable(cells),
|
|
||||||
},
|
|
||||||
geometry: {
|
|
||||||
type: 'Point',
|
|
||||||
coordinates: [lon.value, lat.value],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (geoValues.length > 0) {
|
|
||||||
// parse WKT to geoJSON array
|
|
||||||
features.push(
|
|
||||||
...geoValues.map(wellknown).map(geometry => ({
|
|
||||||
type: 'Feature',
|
|
||||||
properties: {
|
|
||||||
popupContent: createColumnsTable(cells.filter(x => !isWktGeometry(x.value))),
|
|
||||||
},
|
|
||||||
geometry,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (features.length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const geoJson = {
|
|
||||||
type: 'FeatureCollection',
|
|
||||||
features,
|
|
||||||
};
|
|
||||||
|
|
||||||
const geoJsonObj = leaflet
|
|
||||||
.geoJSON(geoJson, {
|
|
||||||
style: function () {
|
|
||||||
return {
|
|
||||||
weight: 2,
|
|
||||||
fillColor: '#ff7800',
|
|
||||||
color: '#ff7800',
|
|
||||||
opacity: 0.8,
|
|
||||||
fillOpacity: 0.4,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
pointToLayer: (feature, latlng) => {
|
|
||||||
return leaflet.circleMarker(latlng, {
|
|
||||||
radius: 7,
|
|
||||||
weight: 2,
|
|
||||||
fillColor: '#ff7800',
|
|
||||||
color: '#ff7800',
|
|
||||||
opacity: 0.8,
|
|
||||||
fillOpacity: 0.4,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onEachFeature: (feature, layer) => {
|
|
||||||
// does this feature have a property named popupContent?
|
|
||||||
if (feature.properties && feature.properties.popupContent) {
|
|
||||||
layer.bindPopup(feature.properties.popupContent);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.addTo(map);
|
|
||||||
// geoJsonObj.bindPopup('This is the Transamerica Pyramid'); //.openPopup();
|
|
||||||
map.fitBounds(geoJsonObj.getBounds());
|
|
||||||
selectionLayers.push(geoJsonObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
map = leaflet.map(refContainer).setView([50, 15], 13);
|
|
||||||
|
|
||||||
leaflet
|
|
||||||
.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
||||||
maxZoom: 19,
|
|
||||||
attribution: '© OpenStreetMap',
|
|
||||||
})
|
|
||||||
.addTo(map);
|
|
||||||
|
|
||||||
addSelectionToMap();
|
|
||||||
// map.fitBounds([
|
|
||||||
// [50, 15],
|
|
||||||
// [50.1, 15],
|
|
||||||
// [50, 15.1],
|
|
||||||
// ]);
|
|
||||||
|
|
||||||
// const marker = leaflet.marker([50, 15]).addTo(map);
|
|
||||||
// <div bind:this={refContainer} class="flex1 map-container" />
|
|
||||||
});
|
|
||||||
|
|
||||||
$: {
|
|
||||||
selection;
|
|
||||||
addSelectionToMap();
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<MapView {selection} />
|
||||||
bind:this={refContainer}
|
|
||||||
class="flex1"
|
|
||||||
use:resizeObserver={true}
|
|
||||||
on:resize={async e => {
|
|
||||||
await tick();
|
|
||||||
map.invalidateSize();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|||||||
@@ -112,6 +112,14 @@
|
|||||||
onClick: () => getCurrentDataGrid().editJsonDocument(),
|
onClick: () => getCurrentDataGrid().editJsonDocument(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
registerCommand({
|
||||||
|
id: 'dataGrid.openSelectionInMap',
|
||||||
|
category: 'Data grid',
|
||||||
|
name: 'Open selection in map',
|
||||||
|
testEnabled: () => getCurrentDataGrid() != null, // ?.openSelectionInMapEnabled(),
|
||||||
|
onClick: () => getCurrentDataGrid().openSelectionInMap(),
|
||||||
|
});
|
||||||
|
|
||||||
registerCommand({
|
registerCommand({
|
||||||
id: 'dataGrid.viewJsonDocument',
|
id: 'dataGrid.viewJsonDocument',
|
||||||
category: 'Data grid',
|
category: 'Data grid',
|
||||||
@@ -306,6 +314,8 @@
|
|||||||
import { apiCall } from '../utility/api';
|
import { apiCall } from '../utility/api';
|
||||||
import getElectron from '../utility/getElectron';
|
import getElectron from '../utility/getElectron';
|
||||||
import { isCtrlOrCommandKey, isMac } from '../utility/common';
|
import { isCtrlOrCommandKey, isMac } from '../utility/common';
|
||||||
|
import { selectionCouldBeShownOnMap } from '../elements/MapView.svelte';
|
||||||
|
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
|
||||||
|
|
||||||
export let onLoadNextData = undefined;
|
export let onLoadNextData = undefined;
|
||||||
export let grider = undefined;
|
export let grider = undefined;
|
||||||
@@ -530,6 +540,23 @@
|
|||||||
openJsonDocument(json);
|
openJsonDocument(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function openSelectionInMap() {
|
||||||
|
const selection = getCellsPublished(selectedCells);
|
||||||
|
if (!selectionCouldBeShownOnMap(selection)) {
|
||||||
|
showModal(ErrorMessageModal, { message: 'There is nothing to be shown on map' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openNewTab({
|
||||||
|
title: 'Map',
|
||||||
|
icon: 'img map',
|
||||||
|
tabComponent: 'MapTab',
|
||||||
|
props: {
|
||||||
|
selection,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
function getSelectedExportableCell() {
|
function getSelectedExportableCell() {
|
||||||
const electron = getElectron();
|
const electron = getElectron();
|
||||||
if (electron && selectedCells.length == 1) {
|
if (electron && selectedCells.length == 1) {
|
||||||
@@ -1426,6 +1453,7 @@
|
|||||||
{ command: 'dataGrid.generateSqlFromData' },
|
{ command: 'dataGrid.generateSqlFromData' },
|
||||||
{ command: 'dataGrid.openFreeTable' },
|
{ command: 'dataGrid.openFreeTable' },
|
||||||
{ command: 'dataGrid.openChartFromSelection' },
|
{ command: 'dataGrid.openChartFromSelection' },
|
||||||
|
{ command: 'dataGrid.openSelectionInMap', hideDisabled: true },
|
||||||
{ placeTag: 'chart' }
|
{ placeTag: 'chart' }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
164
packages/web/src/elements/MapView.svelte
Normal file
164
packages/web/src/elements/MapView.svelte
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
<script lang="ts" context="module">
|
||||||
|
export function selectionCouldBeShownOnMap(selection) {
|
||||||
|
console.log('selection', selection);
|
||||||
|
if (selection.length > 0 && _.find(selection, x => isWktGeometry(x.value))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
selection.find(x => x.column.toLowerCase().includes('lat')) &&
|
||||||
|
(selection.find(x => x.column.toLowerCase().includes('lon')) ||
|
||||||
|
selection.find(x => x.column.toLowerCase().includes('lng')))
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { onMount, tick } from 'svelte';
|
||||||
|
import 'leaflet/dist/leaflet.css';
|
||||||
|
import leaflet from 'leaflet';
|
||||||
|
import wellknown from 'wellknown';
|
||||||
|
import { isWktGeometry } from 'dbgate-tools';
|
||||||
|
import resizeObserver from '../utility/resizeObserver';
|
||||||
|
|
||||||
|
export let selection;
|
||||||
|
|
||||||
|
let refContainer;
|
||||||
|
let map;
|
||||||
|
|
||||||
|
let selectionLayers = [];
|
||||||
|
|
||||||
|
function createColumnsTable(cells) {
|
||||||
|
return `<table>${cells.map(cell => `<tr><td>${cell.column}</td><td>${cell.value}</td></tr>`).join('\n')}</table>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSelectionToMap() {
|
||||||
|
if (!map) return;
|
||||||
|
if (!selection) return;
|
||||||
|
|
||||||
|
for (const selectionLayer of selectionLayers) {
|
||||||
|
selectionLayer.remove();
|
||||||
|
}
|
||||||
|
selectionLayers = [];
|
||||||
|
|
||||||
|
const selectedRows = _.groupBy(selection || [], 'row');
|
||||||
|
|
||||||
|
const features = [];
|
||||||
|
|
||||||
|
for (const rowKey of _.keys(selectedRows)) {
|
||||||
|
const cells = selectedRows[rowKey];
|
||||||
|
const lat = cells.find(x => x.column.toLowerCase().includes('lat'));
|
||||||
|
const lon = cells.find(x => x.column.toLowerCase().includes('lon') || x.column.toLowerCase().includes('lng'));
|
||||||
|
|
||||||
|
const geoValues = cells.map(x => x.value).filter(isWktGeometry);
|
||||||
|
|
||||||
|
if (lat && lon) {
|
||||||
|
features.push({
|
||||||
|
type: 'Feature',
|
||||||
|
properties: {
|
||||||
|
popupContent: createColumnsTable(cells),
|
||||||
|
},
|
||||||
|
geometry: {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: [lon.value, lat.value],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (geoValues.length > 0) {
|
||||||
|
// parse WKT to geoJSON array
|
||||||
|
features.push(
|
||||||
|
...geoValues.map(wellknown).map(geometry => ({
|
||||||
|
type: 'Feature',
|
||||||
|
properties: {
|
||||||
|
popupContent: createColumnsTable(cells.filter(x => !isWktGeometry(x.value))),
|
||||||
|
},
|
||||||
|
geometry,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (features.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const geoJson = {
|
||||||
|
type: 'FeatureCollection',
|
||||||
|
features,
|
||||||
|
};
|
||||||
|
|
||||||
|
const geoJsonObj = leaflet
|
||||||
|
.geoJSON(geoJson, {
|
||||||
|
style: function () {
|
||||||
|
return {
|
||||||
|
weight: 2,
|
||||||
|
fillColor: '#ff7800',
|
||||||
|
color: '#ff7800',
|
||||||
|
opacity: 0.8,
|
||||||
|
fillOpacity: 0.4,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
pointToLayer: (feature, latlng) => {
|
||||||
|
return leaflet.circleMarker(latlng, {
|
||||||
|
radius: 7,
|
||||||
|
weight: 2,
|
||||||
|
fillColor: '#ff7800',
|
||||||
|
color: '#ff7800',
|
||||||
|
opacity: 0.8,
|
||||||
|
fillOpacity: 0.4,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onEachFeature: (feature, layer) => {
|
||||||
|
// does this feature have a property named popupContent?
|
||||||
|
if (feature.properties && feature.properties.popupContent) {
|
||||||
|
layer.bindPopup(feature.properties.popupContent);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.addTo(map);
|
||||||
|
// geoJsonObj.bindPopup('This is the Transamerica Pyramid'); //.openPopup();
|
||||||
|
map.fitBounds(geoJsonObj.getBounds());
|
||||||
|
selectionLayers.push(geoJsonObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
map = leaflet.map(refContainer).setView([50, 15], 13);
|
||||||
|
|
||||||
|
leaflet
|
||||||
|
.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
maxZoom: 19,
|
||||||
|
attribution: '© OpenStreetMap',
|
||||||
|
})
|
||||||
|
.addTo(map);
|
||||||
|
|
||||||
|
addSelectionToMap();
|
||||||
|
// map.fitBounds([
|
||||||
|
// [50, 15],
|
||||||
|
// [50.1, 15],
|
||||||
|
// [50, 15.1],
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
// const marker = leaflet.marker([50, 15]).addTo(map);
|
||||||
|
// <div bind:this={refContainer} class="flex1 map-container" />
|
||||||
|
});
|
||||||
|
|
||||||
|
$: {
|
||||||
|
selection;
|
||||||
|
addSelectionToMap();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={refContainer}
|
||||||
|
class="flex1"
|
||||||
|
use:resizeObserver={true}
|
||||||
|
on:resize={async e => {
|
||||||
|
await tick();
|
||||||
|
map.invalidateSize();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
@@ -165,6 +165,7 @@
|
|||||||
|
|
||||||
'img sort-asc': 'mdi mdi-sort-alphabetical-ascending color-icon-green',
|
'img sort-asc': 'mdi mdi-sort-alphabetical-ascending color-icon-green',
|
||||||
'img sort-desc': 'mdi mdi-sort-alphabetical-descending color-icon-green',
|
'img sort-desc': 'mdi mdi-sort-alphabetical-descending color-icon-green',
|
||||||
|
'img map': 'mdi mdi-map color-icon-blue',
|
||||||
|
|
||||||
'img reference': 'mdi mdi-link-box',
|
'img reference': 'mdi mdi-link-box',
|
||||||
'img link': 'mdi mdi-link',
|
'img link': 'mdi mdi-link',
|
||||||
|
|||||||
8
packages/web/src/tabs/MapTab.svelte
Normal file
8
packages/web/src/tabs/MapTab.svelte
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import MapView from '../elements/MapView.svelte';
|
||||||
|
|
||||||
|
export let selection;
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<MapView {selection} />
|
||||||
@@ -24,6 +24,7 @@ import * as DiagramTab from './DiagramTab.svelte';
|
|||||||
import * as DbKeyDetailTab from './DbKeyDetailTab.svelte';
|
import * as DbKeyDetailTab from './DbKeyDetailTab.svelte';
|
||||||
import * as QueryDataTab from './QueryDataTab.svelte';
|
import * as QueryDataTab from './QueryDataTab.svelte';
|
||||||
import * as ConnectionTab from './ConnectionTab.svelte';
|
import * as ConnectionTab from './ConnectionTab.svelte';
|
||||||
|
import * as MapTab from './MapTab.svelte';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
TableDataTab,
|
TableDataTab,
|
||||||
@@ -52,4 +53,5 @@ export default {
|
|||||||
DbKeyDetailTab,
|
DbKeyDetailTab,
|
||||||
QueryDataTab,
|
QueryDataTab,
|
||||||
ConnectionTab,
|
ConnectionTab,
|
||||||
|
MapTab,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
import { isWktGeometry } from 'dbgate-tools';
|
import { isWktGeometry } from 'dbgate-tools';
|
||||||
|
|
||||||
const formats = [
|
const formats = [
|
||||||
{
|
{
|
||||||
@@ -51,15 +51,7 @@ import { isWktGeometry } from 'dbgate-tools';
|
|||||||
return 'jsonRow';
|
return 'jsonRow';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selection.length > 0 && _.find(selection, x => isWktGeometry(x.value))) {
|
if (selectionCouldBeShownOnMap(selection)) {
|
||||||
return 'map';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
selection.find(x => x.column.toLowerCase().includes('lat')) &&
|
|
||||||
(selection.find(x => x.column.toLowerCase().includes('lon')) ||
|
|
||||||
selection.find(x => x.column.toLowerCase().includes('lng')))
|
|
||||||
) {
|
|
||||||
return 'map';
|
return 'map';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +80,7 @@ import { isWktGeometry } from 'dbgate-tools';
|
|||||||
import TextCellViewNoWrap from '../celldata/TextCellViewNoWrap.svelte';
|
import TextCellViewNoWrap from '../celldata/TextCellViewNoWrap.svelte';
|
||||||
import TextCellViewWrap from '../celldata/TextCellViewWrap.svelte';
|
import TextCellViewWrap from '../celldata/TextCellViewWrap.svelte';
|
||||||
import ErrorInfo from '../elements/ErrorInfo.svelte';
|
import ErrorInfo from '../elements/ErrorInfo.svelte';
|
||||||
|
import { selectionCouldBeShownOnMap } from '../elements/MapView.svelte';
|
||||||
import SelectField from '../forms/SelectField.svelte';
|
import SelectField from '../forms/SelectField.svelte';
|
||||||
import { selectedCellsCallback } from '../stores';
|
import { selectedCellsCallback } from '../stores';
|
||||||
import WidgetTitle from './WidgetTitle.svelte';
|
import WidgetTitle from './WidgetTitle.svelte';
|
||||||
|
|||||||
Reference in New Issue
Block a user