mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-24 12:58:34 +00:00
Feature/google map view (#7828)
* custom overlay * google map clusterer * google controls * update google map in details * fix bug - image dialog does not work after add filters --------- Co-authored-by: zhouwenxuan <aries@Mac.local>
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Icon from '../../icon';
|
import Icon from '../../icon';
|
||||||
import TextTranslation from '../../../utils/text-translation';
|
import TextTranslation from '../../../utils/text-translation';
|
||||||
import { baiduMapKey, gettext } from '../../../utils/constants';
|
import { baiduMapKey, gettext, googleMapKey } from '../../../utils/constants';
|
||||||
import { VIEW_TYPE, VIEW_TYPE_ICON } from '../../../metadata/constants';
|
import { VIEW_TYPE, VIEW_TYPE_ICON } from '../../../metadata/constants';
|
||||||
|
|
||||||
export const KEY_ADD_VIEW_MAP = {
|
export const KEY_ADD_VIEW_MAP = {
|
||||||
@@ -46,7 +46,7 @@ export const getNewViewSubMenu = () => {
|
|||||||
const options = [...ADD_VIEW_OPTIONS];
|
const options = [...ADD_VIEW_OPTIONS];
|
||||||
const hasMapOption = options.some(opt => opt.type === VIEW_TYPE.MAP);
|
const hasMapOption = options.some(opt => opt.type === VIEW_TYPE.MAP);
|
||||||
|
|
||||||
if (!hasMapOption && baiduMapKey) {
|
if (!hasMapOption && (baiduMapKey || googleMapKey)) {
|
||||||
options.push({
|
options.push({
|
||||||
key: KEY_ADD_VIEW_MAP.ADD_MAP,
|
key: KEY_ADD_VIEW_MAP.ADD_MAP,
|
||||||
type: VIEW_TYPE.MAP,
|
type: VIEW_TYPE.MAP,
|
||||||
|
@@ -1,12 +1,41 @@
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { Utils } from '../../../utils/utils';
|
import { Utils } from '../../../utils/utils';
|
||||||
|
import { wgs84_to_gcj02 } from '../../../utils/coord-transform';
|
||||||
|
|
||||||
export function createBMapGeolocationControl(BMapGL, callback) {
|
export const createGeolocationControl = (map) => {
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.className = classnames(
|
||||||
|
'sf-map-control-container sf-map-geolocation-control-container d-flex align-items-center justify-content-center',
|
||||||
|
{ 'sf-map-geolocation-control-mobile': !Utils.isDesktop() }
|
||||||
|
);
|
||||||
|
|
||||||
|
const button = document.createElement('div');
|
||||||
|
button.className = 'sf-map-control sf-map-geolocation-control d-flex align-items-center justify-content-center';
|
||||||
|
button.innerHTML = '<i class="sf-map-control-icon sf3-font sf3-font-current-location"></i>';
|
||||||
|
container.appendChild(button);
|
||||||
|
|
||||||
|
container.addEventListener('click', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const originalClass = container.className;
|
||||||
|
container.className = classnames(originalClass, 'sf-map-control-loading');
|
||||||
|
|
||||||
|
if (navigator.geolocation) {
|
||||||
|
navigator.geolocation.getCurrentPosition((userInfo) => {
|
||||||
|
const gcPosition = wgs84_to_gcj02(userInfo.coords.longitude, userInfo.coords.latitude);
|
||||||
|
map.setCenter(gcPosition);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createBMapGeolocationControl({ anchor, offset, callback }) {
|
||||||
function GeolocationControl() {
|
function GeolocationControl() {
|
||||||
this.defaultAnchor = window.BMAP_ANCHOR_BOTTOM_RIGHT;
|
this.defaultAnchor = anchor || window.BMAP_ANCHOR_BOTTOM_RIGHT;
|
||||||
this.defaultOffset = new BMapGL.Size(30, Utils.isDesktop() ? 30 : 90);
|
this.defaultOffset = new window.BMapGL.Size(offset?.x || 30, offset?.y || 90);
|
||||||
}
|
}
|
||||||
GeolocationControl.prototype = new BMapGL.Control();
|
GeolocationControl.prototype = new window.BMapGL.Control();
|
||||||
GeolocationControl.prototype.initialize = function (map) {
|
GeolocationControl.prototype.initialize = function (map) {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
let className = classnames('sf-map-control-container sf-map-geolocation-control-container d-flex align-items-center justify-content-center', {
|
let className = classnames('sf-map-control-container sf-map-geolocation-control-container d-flex align-items-center justify-content-center', {
|
||||||
@@ -21,7 +50,7 @@ export function createBMapGeolocationControl(BMapGL, callback) {
|
|||||||
div.className = className;
|
div.className = className;
|
||||||
div.onclick = (e) => {
|
div.onclick = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const geolocation = new BMapGL.Geolocation();
|
const geolocation = new window.BMapGL.Geolocation();
|
||||||
div.className = classnames(className, 'sf-map-control-loading');
|
div.className = classnames(className, 'sf-map-control-loading');
|
||||||
geolocation.getCurrentPosition((result) => {
|
geolocation.getCurrentPosition((result) => {
|
||||||
div.className = className;
|
div.className = className;
|
||||||
|
@@ -37,11 +37,21 @@
|
|||||||
background-color: #ccc;
|
background-color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sf-map-control-container.sf-map-geolocation-control-container {
|
||||||
|
right: 30px !important;
|
||||||
|
bottom: 30px !important;
|
||||||
|
}
|
||||||
|
|
||||||
.sf-map-control-container .sf-map-geolocation-control {
|
.sf-map-control-container .sf-map-geolocation-control {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sf-map-control-container.sf-map-zoom-control-container {
|
||||||
|
right: 66px !important;
|
||||||
|
bottom: 30px !important;
|
||||||
|
}
|
||||||
|
|
||||||
.sf-map-control-container .sf-map-zoom-control {
|
.sf-map-control-container .sf-map-zoom-control {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
}
|
}
|
||||||
@@ -64,3 +74,12 @@
|
|||||||
.sf-map-control-container .sf-map-control.disabled {
|
.sf-map-control-container .sf-map-control.disabled {
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.sf-map-control-container.sf-map-geolocation-control-container {
|
||||||
|
bottom: 90px !important;
|
||||||
|
}
|
||||||
|
.sf-map-control-container.sf-map-zoom-control-container {
|
||||||
|
bottom: 90px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,56 +1,97 @@
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { Utils } from '../../../utils/utils';
|
import { Utils } from '../../../utils/utils';
|
||||||
|
import { MIN_ZOOM, MAX_ZOOM } from '../../constants/view/map';
|
||||||
|
|
||||||
export function createBMapZoomControl(BMapGL, { maxZoom, minZoom, offset }, callback) {
|
const buttonClassName = 'sf-map-control sf-map-zoom-control d-flex align-items-center justify-content-center';
|
||||||
|
|
||||||
|
const createZoomContainer = () => {
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.className = classnames(
|
||||||
|
'sf-map-control-container sf-map-zoom-control-container d-flex align-items-center justify-content-center',
|
||||||
|
{ 'sf-map-control-container-mobile': !Utils.isDesktop() }
|
||||||
|
);
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createButton = (innerHTML) => {
|
||||||
|
const button = document.createElement('div');
|
||||||
|
button.className = 'sf-map-control sf-map-zoom-control d-flex align-items-center justify-content-center';
|
||||||
|
button.innerHTML = innerHTML;
|
||||||
|
return button;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createDivider = () => {
|
||||||
|
const divider = document.createElement('div');
|
||||||
|
divider.className = 'sf-map-control-divider';
|
||||||
|
return divider;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateButtonStates = (map, zoomIn, zoomOut) => {
|
||||||
|
const zoomLevel = map.getZoom();
|
||||||
|
zoomIn.className = classnames(buttonClassName, { 'disabled': zoomLevel >= MAX_ZOOM });
|
||||||
|
zoomOut.className = classnames(buttonClassName, { 'disabled': zoomLevel <= MIN_ZOOM });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createZoomControl = (map) => {
|
||||||
|
const container = createZoomContainer();
|
||||||
|
|
||||||
|
const zoomInButton = createButton('<i class="sf-map-control-icon sf3-font sf3-font-zoom-in"></i>');
|
||||||
|
const divider = createDivider();
|
||||||
|
const zoomOutButton = createButton('<i class="sf-map-control-icon sf3-font sf3-font-zoom-out"></i>');
|
||||||
|
|
||||||
|
container.appendChild(zoomInButton);
|
||||||
|
container.appendChild(divider);
|
||||||
|
container.appendChild(zoomOutButton);
|
||||||
|
|
||||||
|
zoomInButton.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const nextZoom = Math.min(map.getZoom() + 1, MAX_ZOOM);
|
||||||
|
map.setZoom(nextZoom);
|
||||||
|
});
|
||||||
|
|
||||||
|
zoomOutButton.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const nextZoom = Math.max(map.getZoom() - 1, MIN_ZOOM);
|
||||||
|
map.setZoom(nextZoom);
|
||||||
|
});
|
||||||
|
|
||||||
|
map.addListener('zoom_changed', () => updateButtonStates(map, zoomInButton, zoomOutButton));
|
||||||
|
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createBMapZoomControl(anchor, offset) {
|
||||||
function ZoomControl() {
|
function ZoomControl() {
|
||||||
this.defaultAnchor = window.BMAP_ANCHOR_BOTTOM_RIGHT;
|
this.defaultAnchor = anchor || window.BMAP_ANCHOR_BOTTOM_RIGHT;
|
||||||
this.defaultOffset = new BMapGL.Size(offset.x, offset.y);
|
this.defaultOffset = new window.BMapGL.Size(offset?.x || 66, offset?.y || 30);
|
||||||
}
|
}
|
||||||
ZoomControl.prototype = new BMapGL.Control();
|
ZoomControl.prototype = new window.BMapGL.Control();
|
||||||
ZoomControl.prototype.initialize = function (map) {
|
ZoomControl.prototype.initialize = function (map) {
|
||||||
const zoomLevel = map.getZoom();
|
const container = createZoomContainer();
|
||||||
const div = document.createElement('div');
|
|
||||||
div.className = classnames('sf-map-control-container sf-map-zoom-control-container d-flex align-items-center justify-content-center', {
|
|
||||||
'sf-map-control-container-mobile': !Utils.isDesktop()
|
|
||||||
});
|
|
||||||
|
|
||||||
const buttonClassName = 'sf-map-control sf-map-zoom-control d-flex align-items-center justify-content-center';
|
const zoomInButton = createButton('<i class="sf-map-control-icon sf3-font sf3-font-zoom-in"></i>');
|
||||||
const zoomInButton = document.createElement('div');
|
const divider = createDivider();
|
||||||
zoomInButton.className = classnames(buttonClassName, { 'disabled': zoomLevel >= maxZoom });
|
const zoomOutButton = createButton('<i class="sf-map-control-icon sf3-font sf3-font-zoom-out"></i>');
|
||||||
zoomInButton.innerHTML = '<i class="sf-map-control-icon sf3-font sf3-font-zoom-in"></i>';
|
|
||||||
div.appendChild(zoomInButton);
|
|
||||||
|
|
||||||
const divider = document.createElement('div');
|
container.appendChild(zoomInButton);
|
||||||
divider.className = 'sf-map-control-divider';
|
container.appendChild(divider);
|
||||||
div.appendChild(divider);
|
container.appendChild(zoomOutButton);
|
||||||
|
|
||||||
const zoomOutButton = document.createElement('div');
|
|
||||||
zoomOutButton.className = classnames(buttonClassName, { 'disabled': zoomLevel <= minZoom });
|
|
||||||
zoomOutButton.innerHTML = '<i class="sf-map-control-icon sf3-font sf3-font-zoom-out"></i>';
|
|
||||||
div.appendChild(zoomOutButton);
|
|
||||||
|
|
||||||
const updateButtonStates = () => {
|
|
||||||
const zoomLevel = map.getZoom();
|
|
||||||
zoomInButton.className = classnames(buttonClassName, { 'disabled': zoomLevel >= maxZoom });
|
|
||||||
zoomOutButton.className = classnames(buttonClassName, { 'disabled': zoomLevel <= minZoom });
|
|
||||||
callback && callback(zoomLevel);
|
|
||||||
};
|
|
||||||
|
|
||||||
zoomInButton.onclick = (e) => {
|
zoomInButton.onclick = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const nextZoom = map.getZoom() + 1;
|
const nextZoom = map.getZoom() + 1;
|
||||||
map.zoomTo(Math.min(nextZoom, maxZoom));
|
map.zoomTo(Math.min(nextZoom, MAX_ZOOM));
|
||||||
};
|
};
|
||||||
|
|
||||||
zoomOutButton.onclick = (e) => {
|
zoomOutButton.onclick = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const nextZoom = map.getZoom() - 1;
|
const nextZoom = map.getZoom() - 1;
|
||||||
map.zoomTo(Math.max(nextZoom, minZoom));
|
map.zoomTo(Math.max(nextZoom, MIN_ZOOM));
|
||||||
};
|
};
|
||||||
|
|
||||||
map.addEventListener('zoomend', updateButtonStates);
|
map.addEventListener('zoomend', () => updateButtonStates(map, zoomInButton, zoomOutButton));
|
||||||
map.getContainer().appendChild(div);
|
map.getContainer().appendChild(container);
|
||||||
return div;
|
return container;
|
||||||
};
|
};
|
||||||
|
|
||||||
return ZoomControl;
|
return ZoomControl;
|
||||||
|
@@ -27,3 +27,8 @@
|
|||||||
.dirent-detail-item-value-map .sf-map-control-container .sf-map-control-divider::before {
|
.dirent-detail-item-value-map .sf-map-control-container .sf-map-control-divider::before {
|
||||||
height: 14px;
|
height: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dirent-detail-item-value-map .sf-map-control-container.sf-map-zoom-control-container {
|
||||||
|
right: 10px !important;
|
||||||
|
bottom: 16px !important;
|
||||||
|
}
|
||||||
|
@@ -14,6 +14,7 @@ import { getColumnDisplayName } from '../../../utils/column';
|
|||||||
import { createBMapZoomControl } from '../../map-controller';
|
import { createBMapZoomControl } from '../../map-controller';
|
||||||
import { Utils } from '../../../../utils/utils';
|
import { Utils } from '../../../../utils/utils';
|
||||||
import { eventBus } from '../../../../components/common/event-bus';
|
import { eventBus } from '../../../../components/common/event-bus';
|
||||||
|
import { createZoomControl } from '../../map-controller/zoom';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
@@ -30,7 +31,6 @@ class Location extends React.Component {
|
|||||||
this.mapType = type;
|
this.mapType = type;
|
||||||
this.mapKey = key;
|
this.mapKey = key;
|
||||||
this.map = null;
|
this.map = null;
|
||||||
this.currentPosition = {};
|
|
||||||
this.state = {
|
this.state = {
|
||||||
address: '',
|
address: '',
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
@@ -50,12 +50,12 @@ class Location extends React.Component {
|
|||||||
const { position, record } = this.props;
|
const { position, record } = this.props;
|
||||||
if (!isValidPosition(position?.lng, position?.lat) || typeof record !== 'object') return;
|
if (!isValidPosition(position?.lng, position?.lat) || typeof record !== 'object') return;
|
||||||
if (prevProps.position?.lng === position?.lng && prevProps.position?.lat === position?.lat) return;
|
if (prevProps.position?.lng === position?.lng && prevProps.position?.lat === position?.lat) return;
|
||||||
this.currentPosition = position;
|
let transformedPos = wgs84_to_gcj02(position.lng, position.lat);
|
||||||
let convertedPos = wgs84_to_gcj02(position.lng, position.lat);
|
|
||||||
if (this.mapType === MAP_TYPE.B_MAP) {
|
if (this.mapType === MAP_TYPE.B_MAP) {
|
||||||
convertedPos = gcj02_to_bd09(convertedPos.lng, convertedPos.lat);
|
transformedPos = gcj02_to_bd09(transformedPos.lng, transformedPos.lat);
|
||||||
}
|
}
|
||||||
this.addMarkerByPosition(convertedPos.lng, convertedPos.lat);
|
this.addMarkerByPosition(transformedPos.lng, transformedPos.lat);
|
||||||
|
|
||||||
this.setState({ address: record._location_translated?.address });
|
this.setState({ address: record._location_translated?.address });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +68,6 @@ class Location extends React.Component {
|
|||||||
|
|
||||||
const { position, record } = this.props;
|
const { position, record } = this.props;
|
||||||
if (!isValidPosition(position?.lng, position?.lat) || typeof record !== 'object') return;
|
if (!isValidPosition(position?.lng, position?.lat) || typeof record !== 'object') return;
|
||||||
this.currentPosition = position;
|
|
||||||
|
|
||||||
this.setState({ isLoading: true, address: record._location_translated?.address });
|
this.setState({ isLoading: true, address: record._location_translated?.address });
|
||||||
if (this.mapType === MAP_TYPE.B_MAP) {
|
if (this.mapType === MAP_TYPE.B_MAP) {
|
||||||
@@ -156,11 +155,15 @@ class Location extends React.Component {
|
|||||||
scaleControl: false,
|
scaleControl: false,
|
||||||
streetViewControl: false,
|
streetViewControl: false,
|
||||||
rotateControl: false,
|
rotateControl: false,
|
||||||
fullscreenControl: false
|
fullscreenControl: false,
|
||||||
|
disableDefaultUI: true,
|
||||||
|
gestureHandling: 'cooperative',
|
||||||
});
|
});
|
||||||
this.map = window.mapInstance;
|
this.map = window.mapInstance;
|
||||||
|
|
||||||
this.addMarkerByPosition(lng, lat);
|
this.addMarkerByPosition(lng, lat);
|
||||||
|
const zoomControl = createZoomControl(this.map);
|
||||||
|
this.map.controls[window.google.maps.ControlPosition.RIGHT_BOTTOM].push(zoomControl);
|
||||||
this.map.setCenter(gcPosition);
|
this.map.setCenter(gcPosition);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@@ -13,3 +13,11 @@ export const MAP_VIEW_TOOLBAR_MODE = {
|
|||||||
MAP: 'map',
|
MAP: 'map',
|
||||||
GALLERY: 'gallery',
|
GALLERY: 'gallery',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const DEFAULT_POSITION = { lng: 104.195, lat: 35.861 };
|
||||||
|
|
||||||
|
export const DEFAULT_ZOOM = 4;
|
||||||
|
|
||||||
|
export const MAX_ZOOM = 21;
|
||||||
|
|
||||||
|
export const MIN_ZOOM = 3;
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { baiduMapKey, gettext } from '../../utils/constants';
|
import { baiduMapKey, gettext, googleMapKey } from '../../utils/constants';
|
||||||
import Icon from '../../components/icon';
|
import Icon from '../../components/icon';
|
||||||
import ItemDropdownMenu from '../../components/dropdown-menu/metadata-item-dropdown-menu';
|
import ItemDropdownMenu from '../../components/dropdown-menu/metadata-item-dropdown-menu';
|
||||||
import toaster from '../../components/toast';
|
import toaster from '../../components/toast';
|
||||||
@@ -77,7 +77,7 @@ const ViewItem = ({
|
|||||||
const convertableViews = Object.values(VIEW_TYPE).filter(type =>
|
const convertableViews = Object.values(VIEW_TYPE).filter(type =>
|
||||||
type !== viewType &&
|
type !== viewType &&
|
||||||
type !== VIEW_TYPE.FACE_RECOGNITION &&
|
type !== VIEW_TYPE.FACE_RECOGNITION &&
|
||||||
!(type === VIEW_TYPE.MAP && !baiduMapKey)
|
!(type === VIEW_TYPE.MAP && !baiduMapKey && !googleMapKey)
|
||||||
);
|
);
|
||||||
value.push({
|
value.push({
|
||||||
key: 'turn',
|
key: 'turn',
|
||||||
|
113
frontend/src/metadata/views/map/baidu.js
Normal file
113
frontend/src/metadata/views/map/baidu.js
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import { appAvatarURL, mediaUrl } from '../../../utils/constants';
|
||||||
|
import { gcj02_to_bd09, wgs84_to_gcj02 } from '../../../utils/coord-transform';
|
||||||
|
import { Utils } from '../../../utils/utils';
|
||||||
|
import { createBMapGeolocationControl, createBMapZoomControl } from '../../components/map-controller';
|
||||||
|
import { MIN_ZOOM, MAX_ZOOM } from '../../constants';
|
||||||
|
import { customAvatarOverlay, customImageOverlay } from './overlay';
|
||||||
|
|
||||||
|
const getPoints = (images) => {
|
||||||
|
if (!window.Cluster || !images) return [];
|
||||||
|
return window.Cluster.pointTransformer(images, (data) => ({
|
||||||
|
point: [data.location.lng, data.location.lat],
|
||||||
|
properties: {
|
||||||
|
id: data.id,
|
||||||
|
src: data.src,
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createBaiduMarkerClusterer = (map, images, onClusterLeaveIds) => {
|
||||||
|
let clickTimeout = null;
|
||||||
|
const cluster = new window.Cluster.View(map, {
|
||||||
|
clusterRadius: 60,
|
||||||
|
updateRealTime: true,
|
||||||
|
fitViewOnClick: false,
|
||||||
|
isAnimation: true,
|
||||||
|
clusterMap: (properties) => ({ src: properties.src, id: properties.id }),
|
||||||
|
clusterReduce: (acc, properties) => {
|
||||||
|
if (!acc.properties) {
|
||||||
|
acc.properties = [];
|
||||||
|
}
|
||||||
|
acc.properties.push(properties);
|
||||||
|
},
|
||||||
|
renderClusterStyle: {
|
||||||
|
type: window.Cluster.ClusterRender.DOM,
|
||||||
|
style: { offsetX: -40, offsetY: -80 },
|
||||||
|
inject: (props) => customImageOverlay(props),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
cluster.setData(getPoints(images));
|
||||||
|
|
||||||
|
cluster.on(window.Cluster.ClusterEvent.CLICK, (element) => {
|
||||||
|
if (clickTimeout) {
|
||||||
|
clearTimeout(clickTimeout);
|
||||||
|
clickTimeout = null;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
clickTimeout = setTimeout(() => {
|
||||||
|
let imageIds = [];
|
||||||
|
if (element.isCluster) {
|
||||||
|
imageIds = cluster.getLeaves(element.id).map(item => item.properties.id).filter(Boolean);
|
||||||
|
} else {
|
||||||
|
imageIds = [element.properties.id];
|
||||||
|
}
|
||||||
|
clickTimeout = null;
|
||||||
|
onClusterLeaveIds(imageIds);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return cluster;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createBaiduMap = ({ type, center, zoom, onMapState }) => {
|
||||||
|
if (!window.BMapGL) return;
|
||||||
|
const map = new window.BMapGL.Map('sf-metadata-map-container', {
|
||||||
|
enableMapClick: false,
|
||||||
|
minZoom: MIN_ZOOM,
|
||||||
|
maxZoom: MAX_ZOOM,
|
||||||
|
mapType: type,
|
||||||
|
});
|
||||||
|
|
||||||
|
map.centerAndZoom(center, zoom);
|
||||||
|
map.enableScrollWheelZoom(true);
|
||||||
|
|
||||||
|
// add controls
|
||||||
|
const ZoomControl = createBMapZoomControl({
|
||||||
|
anchor: window.BMAP_ANCHOR_BOTTOM_RIGHT,
|
||||||
|
offset: new window.BMapGL.Size(66, Utils.isDesktop() ? 30 : 90),
|
||||||
|
});
|
||||||
|
const zoomControl = new ZoomControl();
|
||||||
|
|
||||||
|
const GeolocationControl = createBMapGeolocationControl({
|
||||||
|
anchor: window.BMAP_ANCHOR_BOTTOM_RIGHT,
|
||||||
|
offset: new window.BMapGL.Size(30, Utils.isDesktop() ? 30 : 90),
|
||||||
|
callback: (point) => {
|
||||||
|
const gcPosition = wgs84_to_gcj02(point.lng, point.lat);
|
||||||
|
const bdPosition = gcj02_to_bd09(gcPosition.lng, gcPosition.lat);
|
||||||
|
map.centerAndZoom(new window.BMapGL.Point(bdPosition.lng, bdPosition.lat), map.getZoom());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const geolocationControl = new GeolocationControl();
|
||||||
|
|
||||||
|
map.addControl(zoomControl);
|
||||||
|
map.addControl(geolocationControl);
|
||||||
|
|
||||||
|
map.addEventListener('zoomend', () => onMapState());
|
||||||
|
|
||||||
|
if (navigator.geolocation) {
|
||||||
|
navigator.geolocation.getCurrentPosition((userInfo) => {
|
||||||
|
const gcPosition = wgs84_to_gcj02(userInfo.coords.longitude, userInfo.coords.latitude);
|
||||||
|
const bdPosition = gcj02_to_bd09(gcPosition.lng, gcPosition.lat);
|
||||||
|
const { lng, lat } = bdPosition;
|
||||||
|
const userPosition = new window.BMapGL.Point(lng, lat);
|
||||||
|
const imageUrl = `${mediaUrl}img/marker.png`;
|
||||||
|
const avatarMarker = customAvatarOverlay(userPosition, appAvatarURL, imageUrl);
|
||||||
|
map.addOverlay(avatarMarker);
|
||||||
|
onMapState();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
};
|
103
frontend/src/metadata/views/map/google.js
Normal file
103
frontend/src/metadata/views/map/google.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import { appAvatarURL, googleMapId, mediaUrl } from '../../../utils/constants';
|
||||||
|
import { createGeolocationControl } from '../../components/map-controller/geolocation';
|
||||||
|
import { createZoomControl } from '../../components/map-controller/zoom';
|
||||||
|
import { MIN_ZOOM, MAX_ZOOM } from '../../constants';
|
||||||
|
import { customImageOverlay, googleCustomAvatarOverlay } from './overlay';
|
||||||
|
|
||||||
|
let clickTimeout = null;
|
||||||
|
|
||||||
|
export const createGoogleMarkerClusterer = (map, images, onClusterLeaveIds) => {
|
||||||
|
const markers = images.map(image => {
|
||||||
|
const overlay = customImageOverlay({ isCluster: false, src: image.src });
|
||||||
|
overlay.addEventListener('click', () => {
|
||||||
|
if (clickTimeout) {
|
||||||
|
clearTimeout(clickTimeout);
|
||||||
|
clickTimeout = null;
|
||||||
|
const zoom = map.getZoom();
|
||||||
|
map.setZoom(Math.min(zoom + 1, MAX_ZOOM));
|
||||||
|
map.setCenter(image.wgs_84);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clickTimeout = setTimeout(() => {
|
||||||
|
onClusterLeaveIds([image.id]);
|
||||||
|
clickTimeout = null;
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
|
||||||
|
return new window.google.maps.marker.AdvancedMarkerElement({
|
||||||
|
position: image.wgs_84,
|
||||||
|
map,
|
||||||
|
content: overlay,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return new window.markerClusterer.MarkerClusterer({
|
||||||
|
map,
|
||||||
|
markers,
|
||||||
|
renderer: {
|
||||||
|
render: (cluster) => {
|
||||||
|
const imagesInBounds = images.filter(image => cluster.bounds.contains(image.wgs_84));
|
||||||
|
const overlay = customImageOverlay({ isCluster: true, reduces: { src: imagesInBounds[0].src }, pointCount: cluster.count });
|
||||||
|
overlay.addEventListener('click', () => {
|
||||||
|
if (clickTimeout) {
|
||||||
|
clearTimeout(clickTimeout);
|
||||||
|
clickTimeout = null;
|
||||||
|
const zoom = map.getZoom();
|
||||||
|
map.setZoom(Math.min(zoom + 1, MAX_ZOOM));
|
||||||
|
map.setCenter(cluster.position);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clickTimeout = setTimeout(() => {
|
||||||
|
const imagesInBounds = images.filter(image => cluster.bounds.contains(image.wgs_84));
|
||||||
|
onClusterLeaveIds(imagesInBounds.map(image => image.id));
|
||||||
|
clickTimeout = null;
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
return new window.google.maps.marker.AdvancedMarkerElement({
|
||||||
|
position: cluster.position,
|
||||||
|
content: overlay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClusterClick: () => {},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createGoogleMap = ({ center, zoom, onMapState }) => {
|
||||||
|
if (!window.google?.maps?.Map) return;
|
||||||
|
|
||||||
|
const map = new window.google.maps.Map(document.getElementById('sf-metadata-map-container'), {
|
||||||
|
mapId: googleMapId,
|
||||||
|
center,
|
||||||
|
zoom,
|
||||||
|
mapTypeControl: false,
|
||||||
|
streetViewControl: false,
|
||||||
|
fullscreenControl: false,
|
||||||
|
cameraControl: false,
|
||||||
|
disableDefaultUI: true,
|
||||||
|
mapTypeId: window.google.maps.MapTypeId.ROADMAP,
|
||||||
|
minZoom: MIN_ZOOM,
|
||||||
|
maxZoom: MAX_ZOOM,
|
||||||
|
});
|
||||||
|
|
||||||
|
const zoomControl = createZoomControl(map);
|
||||||
|
const geolocationControl = createGeolocationControl(map);
|
||||||
|
|
||||||
|
map.controls[window.google.maps.ControlPosition.RIGHT_BOTTOM].push(zoomControl);
|
||||||
|
map.controls[window.google.maps.ControlPosition.RIGHT_BOTTOM].push(geolocationControl);
|
||||||
|
|
||||||
|
map.addListener('center_changed', () => onMapState());
|
||||||
|
map.addListener('zoom_changed', () => onMapState());
|
||||||
|
|
||||||
|
if (navigator.geolocation) {
|
||||||
|
navigator.geolocation.getCurrentPosition((userInfo) => {
|
||||||
|
const userPosition = { lat: userInfo.coords.latitude, lng: userInfo.coords.longitude };
|
||||||
|
const imageUrl = `${mediaUrl}img/marker.png`;
|
||||||
|
googleCustomAvatarOverlay(map, userPosition, appAvatarURL, imageUrl);
|
||||||
|
onMapState();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
};
|
||||||
|
|
@@ -5,29 +5,25 @@ import { useMetadataView } from '../../hooks/metadata-view';
|
|||||||
import { Utils } from '../../../utils/utils';
|
import { Utils } from '../../../utils/utils';
|
||||||
import { isValidPosition } from '../../utils/validate';
|
import { isValidPosition } from '../../utils/validate';
|
||||||
import { gcj02_to_bd09, wgs84_to_gcj02 } from '../../../utils/coord-transform';
|
import { gcj02_to_bd09, wgs84_to_gcj02 } from '../../../utils/coord-transform';
|
||||||
import loadBMap, { initMapInfo } from '../../../utils/map-utils';
|
import { initMapInfo, loadBMap, loadGMap } from '../../../utils/map-utils';
|
||||||
import { appAvatarURL, baiduMapKey, fileServerRoot, googleMapKey, mediaUrl, siteRoot, thumbnailSizeForGrid, thumbnailSizeForOriginal } from '../../../utils/constants';
|
import { baiduMapKey, fileServerRoot, googleMapKey, siteRoot, thumbnailSizeForGrid, thumbnailSizeForOriginal } from '../../../utils/constants';
|
||||||
import { MAP_TYPE as MAP_PROVIDER, PRIVATE_FILE_TYPE } from '../../../constants';
|
import { MAP_TYPE as MAP_PROVIDER, PRIVATE_FILE_TYPE } from '../../../constants';
|
||||||
import { EVENT_BUS_TYPE, MAP_TYPE, PREDEFINED_FILE_TYPE_OPTION_KEY, STORAGE_MAP_CENTER_KEY, STORAGE_MAP_TYPE_KEY, STORAGE_MAP_ZOOM_KEY } from '../../constants';
|
import { EVENT_BUS_TYPE, MAP_TYPE, PREDEFINED_FILE_TYPE_OPTION_KEY, STORAGE_MAP_CENTER_KEY, STORAGE_MAP_TYPE_KEY, STORAGE_MAP_ZOOM_KEY, DEFAULT_POSITION, DEFAULT_ZOOM } from '../../constants';
|
||||||
import { createBMapGeolocationControl, createBMapZoomControl } from '../../components/map-controller';
|
|
||||||
import { customAvatarOverlay, customImageOverlay } from './overlay';
|
|
||||||
import ModalPortal from '../../../components/modal-portal';
|
import ModalPortal from '../../../components/modal-portal';
|
||||||
import ImageDialog from '../../../components/dialog/image-dialog';
|
import ImageDialog from '../../../components/dialog/image-dialog';
|
||||||
|
import { createGoogleMap, createGoogleMarkerClusterer } from './google';
|
||||||
|
import { createBaiduMap, createBaiduMarkerClusterer } from './baidu';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
const DEFAULT_POSITION = { lng: 104.195, lat: 35.861 };
|
|
||||||
const DEFAULT_ZOOM = 4;
|
|
||||||
const MAX_ZOOM = 21;
|
|
||||||
const MIN_ZOOM = 3;
|
|
||||||
|
|
||||||
const Map = () => {
|
const Map = () => {
|
||||||
const [imageIndex, setImageIndex] = useState(0);
|
const [imageIndex, setImageIndex] = useState(0);
|
||||||
const [clusterLeaveIds, setClusterLeaveIds] = useState([]);
|
const [clusterLeaveIds, setClusterLeaveIds] = useState([]);
|
||||||
|
const [center, setCenter] = useState(DEFAULT_POSITION);
|
||||||
|
const [zoom, setZoom] = useState(DEFAULT_ZOOM);
|
||||||
|
|
||||||
const mapRef = useRef(null);
|
const mapRef = useRef(null);
|
||||||
const clusterRef = useRef(null);
|
const containerRef = useRef(null);
|
||||||
const clickTimeoutRef = useRef(null);
|
|
||||||
const { metadata, viewID, updateCurrentPath } = useMetadataView();
|
const { metadata, viewID, updateCurrentPath } = useMetadataView();
|
||||||
|
|
||||||
const repoID = window.sfMetadataContext.getSetting('repoID');
|
const repoID = window.sfMetadataContext.getSetting('repoID');
|
||||||
@@ -69,7 +65,8 @@ const Map = () => {
|
|||||||
downloadURL: `${fileServerRoot}repos/${repoID}/files${path}?op=download`,
|
downloadURL: `${fileServerRoot}repos/${repoID}/files${path}?op=download`,
|
||||||
thumbnail,
|
thumbnail,
|
||||||
parentDir,
|
parentDir,
|
||||||
location: bdPosition
|
location: bdPosition,
|
||||||
|
wgs_84: { lng, lat },
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
@@ -78,7 +75,19 @@ const Map = () => {
|
|||||||
const mapInfo = useMemo(() => initMapInfo({ baiduMapKey, googleMapKey }), []);
|
const mapInfo = useMemo(() => initMapInfo({ baiduMapKey, googleMapKey }), []);
|
||||||
const clusterLeaves = useMemo(() => images.filter(image => clusterLeaveIds.includes(image.id)), [images, clusterLeaveIds]);
|
const clusterLeaves = useMemo(() => images.filter(image => clusterLeaveIds.includes(image.id)), [images, clusterLeaveIds]);
|
||||||
|
|
||||||
const getPoints = useCallback(() => {
|
const onMapState = useCallback(() => {
|
||||||
|
if (!mapRef.current) return;
|
||||||
|
const point = mapRef.current.getCenter && mapRef.current.getCenter();
|
||||||
|
const zoom = mapRef.current.getZoom && mapRef.current.getZoom();
|
||||||
|
window.sfMetadataContext.localStorage.setItem(STORAGE_MAP_CENTER_KEY, point);
|
||||||
|
window.sfMetadataContext.localStorage.setItem(STORAGE_MAP_ZOOM_KEY, zoom);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onClusterLeaveIds = useCallback((ids) => {
|
||||||
|
setClusterLeaveIds(ids);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getPoints = useCallback((images) => {
|
||||||
if (!window.Cluster || !images) return [];
|
if (!window.Cluster || !images) return [];
|
||||||
return window.Cluster.pointTransformer(images, (data) => ({
|
return window.Cluster.pointTransformer(images, (data) => ({
|
||||||
point: [data.location.lng, data.location.lat],
|
point: [data.location.lng, data.location.lat],
|
||||||
@@ -87,141 +96,23 @@ const Map = () => {
|
|||||||
src: data.src,
|
src: data.src,
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}, [images]);
|
|
||||||
|
|
||||||
const saveMapState = useCallback(() => {
|
|
||||||
if (!mapRef.current) return;
|
|
||||||
const point = mapRef.current.getCenter && mapRef.current.getCenter();
|
|
||||||
const zoom = mapRef.current.getZoom && mapRef.current.getZoom();
|
|
||||||
window.sfMetadataContext.localStorage.setItem(STORAGE_MAP_CENTER_KEY, point);
|
|
||||||
window.sfMetadataContext.localStorage.setItem(STORAGE_MAP_ZOOM_KEY, zoom);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const addMapController = useCallback(() => {
|
|
||||||
const offset = { x: 66, y: Utils.isDesktop() ? 30 : 90 };
|
|
||||||
const ZoomControl = createBMapZoomControl(window.BMapGL, { maxZoom: MAX_ZOOM, minZoom: MIN_ZOOM, offset }, saveMapState);
|
|
||||||
const zoomControl = new ZoomControl();
|
|
||||||
const GeolocationControl = createBMapGeolocationControl(window.BMapGL, (point) => {
|
|
||||||
point && mapRef.current && mapRef.current.setCenter(point);
|
|
||||||
});
|
|
||||||
|
|
||||||
const geolocationControl = new GeolocationControl();
|
|
||||||
mapRef.current.addControl(zoomControl);
|
|
||||||
mapRef.current.addControl(geolocationControl);
|
|
||||||
}, [saveMapState]);
|
|
||||||
|
|
||||||
const initializeUserMarker = useCallback((centerPoint) => {
|
|
||||||
if (!window.BMapGL || !mapRef.current) return;
|
|
||||||
const imageUrl = `${mediaUrl}img/marker.png`;
|
|
||||||
const avatarMarker = customAvatarOverlay(centerPoint, appAvatarURL, imageUrl);
|
|
||||||
mapRef.current.addOverlay(avatarMarker);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getBMapType = useCallback((type) => {
|
|
||||||
switch (type) {
|
|
||||||
case MAP_TYPE.SATELLITE: {
|
|
||||||
return window.BMAP_EARTH_MAP;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return window.BMAP_NORMAL_MAP;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const loadMapState = useCallback(() => {
|
|
||||||
const savedCenter = window.sfMetadataContext.localStorage.getItem(STORAGE_MAP_CENTER_KEY) || DEFAULT_POSITION;
|
|
||||||
const savedZoom = window.sfMetadataContext.localStorage.getItem(STORAGE_MAP_ZOOM_KEY) || DEFAULT_ZOOM;
|
|
||||||
return { center: savedCenter, zoom: savedZoom };
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const initializeCluster = useCallback(() => {
|
|
||||||
clusterRef.current = new window.Cluster.View(mapRef.current, {
|
|
||||||
clusterRadius: 60,
|
|
||||||
updateRealTime: true,
|
|
||||||
fitViewOnClick: false,
|
|
||||||
isAnimation: true,
|
|
||||||
clusterMap: (properties) => ({ src: properties.src, id: properties.id }),
|
|
||||||
clusterReduce: (acc, properties) => {
|
|
||||||
if (!acc.properties) {
|
|
||||||
acc.properties = [];
|
|
||||||
}
|
|
||||||
acc.properties.push(properties);
|
|
||||||
},
|
|
||||||
renderClusterStyle: {
|
|
||||||
type: window.Cluster.ClusterRender.DOM,
|
|
||||||
style: { offsetX: -40, offsetY: -80 },
|
|
||||||
inject: (props) => customImageOverlay(props),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
clusterRef.current.setData(getPoints());
|
|
||||||
|
|
||||||
clusterRef.current.on(window.Cluster.ClusterEvent.CLICK, (element) => {
|
|
||||||
if (clickTimeoutRef.current) {
|
|
||||||
clearTimeout(clickTimeoutRef.current);
|
|
||||||
clickTimeoutRef.current = null;
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
clickTimeoutRef.current = setTimeout(() => {
|
|
||||||
let imageIds = [];
|
|
||||||
if (element.isCluster) {
|
|
||||||
imageIds = clusterRef.current.getLeaves(element.id).map(item => item.properties.id).filter(Boolean);
|
|
||||||
} else {
|
|
||||||
imageIds = [element.properties.id];
|
|
||||||
}
|
|
||||||
clickTimeoutRef.current = null;
|
|
||||||
setClusterLeaveIds(imageIds);
|
|
||||||
}, 300);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
window.BMapCluster = clusterRef.current;
|
|
||||||
}, [getPoints]);
|
|
||||||
|
|
||||||
const renderBaiduMap = useCallback(() => {
|
const renderBaiduMap = useCallback(() => {
|
||||||
if (!window.BMapGL.Map) return;
|
if (!window.BMapGL.Map) return;
|
||||||
let { center, zoom } = loadMapState();
|
mapRef.current = createBaiduMap({ images, center, zoom, getPoints, onMapState, onClusterLeaveIds });
|
||||||
let userPosition = { lng: 116.40396418840683, lat: 39.915106021711345 };
|
createBaiduMarkerClusterer(mapRef.current, images, onClusterLeaveIds);
|
||||||
// ask for user location
|
|
||||||
if (navigator.geolocation) {
|
|
||||||
navigator.geolocation.getCurrentPosition((userInfo) => {
|
|
||||||
const gcPosition = wgs84_to_gcj02(userInfo.coords.longitude, userInfo.coords.latitude);
|
|
||||||
const bdPosition = gcj02_to_bd09(gcPosition.lng, gcPosition.lat);
|
|
||||||
const { lng, lat } = bdPosition;
|
|
||||||
userPosition = new window.BMapGL.Point(lng, lat);
|
|
||||||
center = userPosition;
|
|
||||||
window.sfMetadataContext.localStorage.setItem(STORAGE_MAP_CENTER_KEY, center);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const mapTypeValue = window.sfMetadataContext.localStorage.getItem(STORAGE_MAP_TYPE_KEY);
|
|
||||||
|
|
||||||
if (!window.BMapInstance) {
|
window.mapViewInstance = mapRef.current;
|
||||||
mapRef.current = new window.BMapGL.Map('sf-metadata-map-container', {
|
}, [images, center, zoom, getPoints, onMapState, onClusterLeaveIds]);
|
||||||
enableMapClick: false,
|
|
||||||
minZoom: MIN_ZOOM,
|
|
||||||
maxZoom: MAX_ZOOM,
|
|
||||||
mapType: getBMapType(mapTypeValue),
|
|
||||||
});
|
|
||||||
window.BMapInstance = mapRef.current;
|
|
||||||
|
|
||||||
if (isValidPosition(center?.lng, center?.lat)) {
|
const renderGoogleMap = useCallback(() => {
|
||||||
mapRef.current.centerAndZoom(center, zoom);
|
if (!window.google?.maps?.Map) return;
|
||||||
}
|
mapRef.current = createGoogleMap({ center, zoom, onMapState });
|
||||||
|
createGoogleMarkerClusterer(mapRef.current, images, onClusterLeaveIds);
|
||||||
|
|
||||||
mapRef.current.enableScrollWheelZoom(true);
|
window.mapViewInstance = mapRef.current;
|
||||||
addMapController();
|
}, [images, center, zoom, onMapState, onClusterLeaveIds]);
|
||||||
|
|
||||||
initializeUserMarker(userPosition);
|
|
||||||
initializeCluster();
|
|
||||||
} else {
|
|
||||||
const viewDom = document.getElementById('sf-metadata-view-map');
|
|
||||||
const container = window.BMapInstance.getContainer();
|
|
||||||
viewDom.replaceChild(container, mapRef.current);
|
|
||||||
|
|
||||||
mapRef.current = window.BMapInstance;
|
|
||||||
clusterRef.current = window.BMapCluster;
|
|
||||||
clusterRef.current.setData(getPoints());
|
|
||||||
}
|
|
||||||
}, [addMapController, initializeCluster, initializeUserMarker, getBMapType, loadMapState, getPoints]);
|
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
const handleClose = useCallback(() => {
|
||||||
setImageIndex(0);
|
setImageIndex(0);
|
||||||
@@ -236,21 +127,60 @@ const Map = () => {
|
|||||||
setImageIndex((imageIndex + 1) % clusterLeaves.length);
|
setImageIndex((imageIndex + 1) % clusterLeaves.length);
|
||||||
}, [imageIndex, clusterLeaves.length]);
|
}, [imageIndex, clusterLeaves.length]);
|
||||||
|
|
||||||
|
const onMapTypeChange = useCallback((newType) => {
|
||||||
|
window.sfMetadataContext.localStorage.setItem(STORAGE_MAP_TYPE_KEY, newType);
|
||||||
|
|
||||||
|
if (mapInfo.type === MAP_PROVIDER.B_MAP) {
|
||||||
|
const baiduMapType = {
|
||||||
|
[MAP_TYPE.MAP]: window.BMAP_NORMAL_MAP,
|
||||||
|
[MAP_TYPE.SATELLITE]: window.BMAP_SATELLITE_MAP
|
||||||
|
}[newType] || window.BMAP_NORMAL_MAP;
|
||||||
|
|
||||||
|
mapRef.current?.setMapType(baiduMapType);
|
||||||
|
} else if (mapInfo.type === MAP_PROVIDER.G_MAP) {
|
||||||
|
const googleMapType = {
|
||||||
|
[MAP_TYPE.MAP]: window.google.maps.MapTypeId.ROADMAP,
|
||||||
|
[MAP_TYPE.SATELLITE]: window.google.maps.MapTypeId.HYBRID
|
||||||
|
}[newType] || window.google.maps.MapTypeId.ROADMAP;
|
||||||
|
|
||||||
|
mapRef.current?.setMapTypeId(googleMapType);
|
||||||
|
}
|
||||||
|
|
||||||
|
mapRef.current?.setCenter(mapRef.current.getCenter());
|
||||||
|
}, [mapInfo.type]);
|
||||||
|
|
||||||
|
const onClearMapInstance = useCallback(() => {
|
||||||
|
if (window.mapViewInstance) {
|
||||||
|
if (mapInfo.type === MAP_PROVIDER.B_MAP) {
|
||||||
|
window.mapViewInstance.destroy();
|
||||||
|
} else if (mapInfo.type === MAP_PROVIDER.G_MAP) {
|
||||||
|
window.mapViewInstance.setMap(null);
|
||||||
|
}
|
||||||
|
delete window.mapViewInstance;
|
||||||
|
}
|
||||||
|
mapRef.current = null;
|
||||||
|
}, [mapInfo.type]);
|
||||||
|
|
||||||
|
const loadMapState = useCallback(() => {
|
||||||
|
const savedCenter = window.sfMetadataContext.localStorage.getItem(STORAGE_MAP_CENTER_KEY) || DEFAULT_POSITION;
|
||||||
|
const savedZoom = window.sfMetadataContext.localStorage.getItem(STORAGE_MAP_ZOOM_KEY) || DEFAULT_ZOOM;
|
||||||
|
setCenter(savedCenter);
|
||||||
|
setZoom(savedZoom);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateCurrentPath(`/${PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES}/${viewID}`);
|
updateCurrentPath(`/${PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES}/${viewID}`);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const modifyMapTypeSubscribe = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_MAP_TYPE, (newType) => {
|
loadMapState();
|
||||||
window.sfMetadataContext.localStorage.setItem(STORAGE_MAP_TYPE_KEY, newType);
|
const unsubscribeModifyMapType = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_MAP_TYPE, onMapTypeChange);
|
||||||
const mapType = getBMapType(newType);
|
const unsubscribeClearMapInstance = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.CLEAR_MAP_INSTANCE, onClearMapInstance);
|
||||||
mapRef.current && mapRef.current.setMapType(mapType);
|
|
||||||
mapRef.current.setCenter(mapRef.current.getCenter());
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
modifyMapTypeSubscribe();
|
unsubscribeModifyMapType();
|
||||||
|
unsubscribeClearMapInstance();
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@@ -258,18 +188,34 @@ const Map = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mapInfo.type === MAP_PROVIDER.B_MAP) {
|
if (mapInfo.type === MAP_PROVIDER.B_MAP) {
|
||||||
loadBMap(mapInfo.key).then(() => renderBaiduMap());
|
if (!window.mapViewInstance) {
|
||||||
return () => {
|
loadBMap(mapInfo.key).then(() => renderBaiduMap());
|
||||||
window.renderMap = null;
|
} else {
|
||||||
};
|
const viewDom = document.getElementById('sf-metadata-view-map');
|
||||||
|
const container = window.mapViewInstance.getContainer();
|
||||||
|
viewDom.replaceChild(container, containerRef.current);
|
||||||
|
|
||||||
|
mapRef.current = window.mapViewInstance;
|
||||||
|
createBaiduMarkerClusterer(mapRef.current, images, onClusterLeaveIds);
|
||||||
|
}
|
||||||
|
} else if (mapInfo.type === MAP_PROVIDER.G_MAP) {
|
||||||
|
if (!window.mapViewInstance) {
|
||||||
|
loadGMap(mapInfo.key).then(() => renderGoogleMap());
|
||||||
|
} else {
|
||||||
|
const viewDom = document.getElementById('sf-metadata-view-map');
|
||||||
|
const container = window.mapViewInstance.getDiv();
|
||||||
|
viewDom.replaceChild(container, containerRef.current);
|
||||||
|
|
||||||
|
mapRef.current = window.mapViewInstance;
|
||||||
|
createGoogleMarkerClusterer(mapRef.current, images, onClusterLeaveIds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sf-metadata-view-map" id="sf-metadata-view-map">
|
<div className="sf-metadata-view-map" id="sf-metadata-view-map">
|
||||||
<div ref={mapRef} className="sf-metadata-map-container" id="sf-metadata-map-container"></div>
|
<div ref={containerRef} className="sf-metadata-map-container" id="sf-metadata-map-container"></div>
|
||||||
{clusterLeaveIds.length > 0 && (
|
{clusterLeaveIds.length > 0 && (
|
||||||
<ModalPortal>
|
<ModalPortal>
|
||||||
<ImageDialog
|
<ImageDialog
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
const customAvatarOverlay = (point, avatarUrl, bgUrl, width = 20, height = 25) => {
|
export const customAvatarOverlay = (point, avatarUrl, bgUrl, width = 20, height = 25) => {
|
||||||
class AvatarOverlay extends window.BMapGL.Overlay {
|
class AvatarOverlay extends window.BMapGL.Overlay {
|
||||||
constructor(point, avatarUrl, bgUrl, width, height) {
|
constructor(point, avatarUrl, bgUrl, width, height) {
|
||||||
super();
|
super();
|
||||||
@@ -44,4 +44,70 @@ const customAvatarOverlay = (point, avatarUrl, bgUrl, width = 20, height = 25) =
|
|||||||
return new AvatarOverlay(point, avatarUrl, bgUrl, width, height);
|
return new AvatarOverlay(point, avatarUrl, bgUrl, width, height);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default customAvatarOverlay;
|
export const googleCustomAvatarOverlay = (map, position, avatarUrl, bgUrl, width = 20, height = 25) => {
|
||||||
|
class AvatarOverlay extends window.google.maps.OverlayView {
|
||||||
|
constructor(map, position, avatarUrl, bgUrl, width, height) {
|
||||||
|
super();
|
||||||
|
this._position = position;
|
||||||
|
this._avatarUrl = avatarUrl;
|
||||||
|
this._bgUrl = bgUrl;
|
||||||
|
this._width = width;
|
||||||
|
this._height = height;
|
||||||
|
this._div = null;
|
||||||
|
this.setMap(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
onAdd() {
|
||||||
|
this.createDomElements();
|
||||||
|
const panes = this.getPanes();
|
||||||
|
panes.overlayLayer.appendChild(this._div);
|
||||||
|
}
|
||||||
|
|
||||||
|
draw() {
|
||||||
|
const overlayProjection = this.getProjection();
|
||||||
|
const pos = overlayProjection.fromLatLngToDivPixel(this._position);
|
||||||
|
|
||||||
|
this._div.style.left = `${pos.x - this._width / 2}px`;
|
||||||
|
this._div.style.top = `${pos.y - (this._height * 7) / 10}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
onRemove() {
|
||||||
|
this._div.parentNode?.removeChild(this._div);
|
||||||
|
this._div = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
createDomElements() {
|
||||||
|
this._div = document.createElement('div');
|
||||||
|
this._div.className = 'custom-avatar-overlay';
|
||||||
|
Object.assign(this._div.style, {
|
||||||
|
position: 'absolute',
|
||||||
|
width: `${this._width}px`,
|
||||||
|
height: `${this._height}px`,
|
||||||
|
backgroundImage: `url(${this._bgUrl})`,
|
||||||
|
backgroundPosition: '.5px 0px',
|
||||||
|
display: 'flex',
|
||||||
|
padding: '2px 2.5px 0 2px'
|
||||||
|
});
|
||||||
|
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.src = this._avatarUrl;
|
||||||
|
Object.assign(img.style, {
|
||||||
|
width: '16px',
|
||||||
|
height: '16px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
display: 'block'
|
||||||
|
});
|
||||||
|
|
||||||
|
this._div.appendChild(img);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AvatarOverlay(
|
||||||
|
map,
|
||||||
|
position,
|
||||||
|
avatarUrl,
|
||||||
|
bgUrl,
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
import customAvatarOverlay from './custom-avatar-overlay';
|
import { customAvatarOverlay, googleCustomAvatarOverlay } from './custom-avatar-overlay';
|
||||||
import customImageOverlay from './custom-image-overlay';
|
import customImageOverlay from './custom-image-overlay';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
customAvatarOverlay,
|
customAvatarOverlay,
|
||||||
|
googleCustomAvatarOverlay,
|
||||||
customImageOverlay
|
customImageOverlay
|
||||||
};
|
};
|
||||||
|
@@ -8,26 +8,27 @@ export const initMapInfo = ({ baiduMapKey, googleMapKey, mineMapKey }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const loadMapSource = (type, key, callback) => {
|
export const loadMapSource = (type, key, callback) => {
|
||||||
if (!type || !key) return;
|
|
||||||
let scriptUrl = '';
|
let scriptUrl = '';
|
||||||
const sourceId = 'map-source-script';
|
const sourceId = 'map-source-script';
|
||||||
|
|
||||||
if (document.getElementById(sourceId)) return;
|
if (document.getElementById(sourceId)) return;
|
||||||
let script = document.createElement('script');
|
|
||||||
script.type = 'text/javascript';
|
|
||||||
script.id = sourceId;
|
|
||||||
if (type === MAP_TYPE.B_MAP) {
|
if (type === MAP_TYPE.B_MAP) {
|
||||||
scriptUrl = `https://api.map.baidu.com/api?type=webgl&v=3.0&ak=${key}&callback=renderBaiduMap`;
|
scriptUrl = `https://api.map.baidu.com/api?type=webgl&v=3.0&ak=${key}&callback=renderBaiduMap`;
|
||||||
} else if (type === MAP_TYPE.G_MAP) {
|
} else if (type === MAP_TYPE.G_MAP) {
|
||||||
scriptUrl = `https://maps.googleapis.com/maps/api/js?key=${key}&callback=renderGoogleMap&libraries=marker&v=weekly`;
|
scriptUrl = `https://maps.googleapis.com/maps/api/js?key=${key}&libraries=marker,geometry&v=weekly&callback=renderGoogleMap`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scriptUrl) {
|
if (scriptUrl) {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.id = sourceId;
|
||||||
script.src = scriptUrl;
|
script.src = scriptUrl;
|
||||||
|
script.onload = callback;
|
||||||
document.body.appendChild(script);
|
document.body.appendChild(script);
|
||||||
}
|
}
|
||||||
callback && callback();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function loadBMap(ak) {
|
export function loadBMap(ak) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (typeof window.BMapGL !== 'undefined' && document.querySelector(`script[src*="${mediaUrl}js/map/cluster.js"]`)) {
|
if (typeof window.BMapGL !== 'undefined' && document.querySelector(`script[src*="${mediaUrl}js/map/cluster.js"]`)) {
|
||||||
resolve(true);
|
resolve(true);
|
||||||
@@ -57,6 +58,36 @@ export function asyncLoadBaiduJs(ak) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function loadGMap(ak) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (typeof window.google !== 'undefined' && document.querySelector(`script[src*="${mediaUrl}js/map/cluster.js"]`)) {
|
||||||
|
resolve(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
asyncLoadGMapJs(ak)
|
||||||
|
.then(() => asyncLoadJs('https://unpkg.com/@googlemaps/markerclusterer/dist/index.min.js'))
|
||||||
|
.then(() => resolve(true))
|
||||||
|
.catch((err) => reject(err));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function asyncLoadGMapJs(key) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (typeof window.google !== 'undefined') {
|
||||||
|
resolve(window.google);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.renderGoogleMap = function () {
|
||||||
|
resolve(window.google);
|
||||||
|
};
|
||||||
|
let script = document.createElement('script');
|
||||||
|
script.type = 'text/javascript';
|
||||||
|
script.src = `https://maps.googleapis.com/maps/api/js?key=${key}&libraries=marker,geometry&v=weekly&callback=renderGoogleMap&loading=async`;
|
||||||
|
script.onerror = reject;
|
||||||
|
document.body.appendChild(script);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function asyncLoadJs(url) {
|
export function asyncLoadJs(url) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let script = document.createElement('script');
|
let script = document.createElement('script');
|
||||||
|
Reference in New Issue
Block a user