Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ node_modules/
icons/build
static/themes.json
dist/
.vscode/
2 changes: 2 additions & 0 deletions appConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import ScratchDrawingPlugin from './plugins/ScratchDrawing';
import SettingsPlugin from './plugins/Settings';
import SharePlugin from './plugins/Share';
import StartupMarkerPlugin from './plugins/StartupMarker';
import StreetViewPlugin from './plugins/StreetView';
import TaskButtonPlugin from './plugins/TaskButton';
import ThemeSwitcherPlugin from './plugins/ThemeSwitcher';
import TimeManagerPlugin from './plugins/TimeManager';
Expand Down Expand Up @@ -126,6 +127,7 @@ export default {
SettingsPlugin: SettingsPlugin,
SharePlugin: SharePlugin,
StartupMarkerPlugin: StartupMarkerPlugin,
StreetViewPlugin: StreetViewPlugin,
TaskButtonPlugin: TaskButtonPlugin,
ThemeSwitcherPlugin: ThemeSwitcherPlugin,
TimeManagerPlugin: TimeManagerPlugin,
Expand Down
1 change: 0 additions & 1 deletion components/map3d/Map3D.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ import Tiles3DStyle from './utils/Tiles3DStyle';
import './style/Map3D.css';



// Ensures unUnload is called *after* all other children have unmounted
class UnloadWrapper extends React.Component {
static propTypes = {
Expand Down
13,577 changes: 13,577 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

311 changes: 311 additions & 0 deletions plugins/StreetView.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
import React from 'react';
import { connect } from 'react-redux';

import Feature from 'ol/Feature';
import Point from 'ol/geom/Point';
import { Translate } from 'ol/interaction';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import { Style, Icon} from 'ol/style';
import PropTypes from 'prop-types';

import ResizeableWindow from '../components/ResizeableWindow';
import Spinner from '../components/widgets/Spinner';
import CoordinatesUtils from '../utils/CoordinatesUtils';
import LocaleUtils from '../utils/LocaleUtils';
import MapUtils from '../utils/MapUtils';

class StreetView extends React.Component {
static propTypes = {
googleMapsApiKey: PropTypes.string,
map: PropTypes.object,
setCurrentTask: PropTypes.func,
task: PropTypes.string
};

state = {
streetViewReady: false
};

isUpdatingFromMap = false;
isUpdatingFromStreetView = false;
markerLayer = null;
markerFeature = null;
translateInteraction = null;


createMarkerLayer = () => {
const olMap = MapUtils.getHook(MapUtils.GET_MAP);

if (!olMap) {
setTimeout(() => this.createMarkerLayer(), 200);
return;
}

// Create vector source and layer
const vectorSource = new VectorSource();
this.markerLayer = new VectorLayer({
source: vectorSource,
zIndex: 999
});

olMap.addLayer(this.markerLayer);

// Create translate interaction for dragging
this.translateInteraction = new Translate({
layers: [this.markerLayer]
});

this.translateInteraction.on('translateend', (event) => {
if (this.isUpdatingFromStreetView) return;

const feature = event.features.getArray()[0];
const coords = feature.getGeometry().getCoordinates();

const mapCrs = this.props.map?.projection || 'EPSG:3857';
const [lng, lat] = CoordinatesUtils.reproject(coords, mapCrs, 'EPSG:4326');

this.updateStreetViewFromMarker(lat, lng);
});

olMap.addInteraction(this.translateInteraction);
};

removeMarkerLayer = () => {
const olMap = MapUtils.getHook(MapUtils.GET_MAP);

if (!olMap) return;

if (this.translateInteraction) {
olMap.removeInteraction(this.translateInteraction);
this.translateInteraction = null;
}
if (this.markerLayer) {
olMap.removeLayer(this.markerLayer);
this.markerLayer = null;
this.markerFeature = null;
}
};

componentDidMount() {
this.initializeStreetView();
}

componentDidUpdate(prevProps) {
if (this.props.task === "StreetView" && prevProps.task !== "StreetView") {
setTimeout(() => {
this.initializeStreetView();
}, 200);
}

if (prevProps.task === "StreetView" && this.props.task !== "StreetView") {
this.removeMarkerLayer();
}
}

componentWillUnmount() {
this.removeMarkerLayer();
}


initializeStreetView = () => {
setTimeout(() => {
this.loadGoogleMaps();
}, 100);
};

loadGoogleMaps = () => {
if (!window.google) {
const script = document.createElement('script');
script.src = `https://maps.googleapis.com/maps/api/js?key=${this.props.googleMapsApiKey}`;
script.async = true;
script.onload = () => this.initStreetView();
document.head.appendChild(script);
} else {
this.initStreetView();
}
};

initStreetView = () => {
const container = document.getElementById('streetview-container');

if (!container) {
return;
}
// Check if map is available
if (!this.props.map || !this.props.map.center) {
setTimeout(() => this.initStreetView(), 200);
return;
}
const mapCenter = this.props.map.center;
const mapCrs = this.props.map?.projection || 'EPSG:3857';
const [lng, lat] = CoordinatesUtils.reproject(mapCenter, mapCrs, 'EPSG:4326');

const panorama = new window.google.maps.StreetViewPanorama(
container,
{
position: { lat: lat, lng: lng },
pov: {
heading: 0,
pitch: 0
},
zoom: 1,
addressControl: true,
linksControl: true,
panControl: true,
enableCloseButton: false
}
);

this.panorama = panorama;
this.setState({ streetViewReady: true });

panorama.addListener('position_changed', () => {
if (!this.isUpdatingFromMap) {
this.isUpdatingFromStreetView = true;
this.updateMarkerFromStreetView();
setTimeout(() => {
this.isUpdatingFromStreetView = false;
}, 100);
}
});

panorama.addListener('pov_changed', () => {
if (!this.isUpdatingFromMap) {
this.isUpdatingFromStreetView = true;
this.updateMarkerFromStreetView();
setTimeout(() => {
this.isUpdatingFromStreetView = false;
}, 100);
}
});

this.createMarkerLayer();
this.updateMarkerFromStreetView();
};
updateMarkerFromStreetView = () => {
if (!this.panorama || !this.markerLayer) return;

const position = this.panorama.getPosition();
const pov = this.panorama.getPov();

if (!position) return;

const lat = position.lat();
const lng = position.lng();
const heading = pov.heading;

const mapCrs = this.props.map?.projection || 'EPSG:3857';
const coords = CoordinatesUtils.reproject([lng, lat], 'EPSG:4326', mapCrs);

// Create or update feature
if (!this.markerFeature) {
this.markerFeature = new Feature({
geometry: new Point(coords)
});
this.markerLayer.getSource().addFeature(this.markerFeature);
} else {
this.markerFeature.getGeometry().setCoordinates(coords);
}

// Create simple triangular delta arrow using Canvas
const canvas = document.createElement('canvas');
canvas.width = 40;
canvas.height = 40;
const ctx = canvas.getContext('2d');

const centerX = 20;
const centerY = 20;

// Save context and rotate
ctx.save();
ctx.translate(centerX, centerY);
ctx.rotate((heading * Math.PI) / 180);
ctx.translate(-centerX, -centerY);

// Draw white outline triangle (larger)
ctx.beginPath();
ctx.moveTo(centerX, 5); // Top point
ctx.lineTo(centerX - 10, 28); // Bottom left
ctx.lineTo(centerX, 24); // Bottom center, and a bit up
ctx.lineTo(centerX + 10, 28); // Bottom right
ctx.closePath();
ctx.fillStyle = 'white';
ctx.fill();

// Draw orange/yellow triangle (inner)
ctx.beginPath();
ctx.moveTo(centerX, 8); // Top point
ctx.lineTo(centerX - 8, 26); // Bottom left
ctx.lineTo(centerX, 22); // Bottom center, and a bit up
ctx.lineTo(centerX + 8, 26); // Bottom right
ctx.closePath();
ctx.fillStyle = '#FFA500'; // Orange
ctx.fill();

ctx.restore();

// Create style with the canvas
const arrowStyle = new Style({
image: new Icon({
img: canvas,
imgSize: [40, 40],
anchor: [0.5, 0.5]
})
});

this.markerFeature.setStyle(arrowStyle);
};
updateStreetViewFromMarker = (lat, lng) => {
if (!this.panorama || this.isUpdatingFromStreetView) return;

this.isUpdatingFromMap = true;

this.panorama.setPosition({ lat, lng });

setTimeout(() => {
this.isUpdatingFromMap = false;
}, 100);
};

render() {
if (this.props.task === "StreetView") {
return (
<ResizeableWindow
dockable="bottom"
icon="street"
initialHeight={350}
initialWidth={800}
initiallyDocked
onClose={() => this.props.setCurrentTask(null)}
title="Street View"
>
<div
id="streetview-container"
role="body"
style={{
width: '100%',
height: '100%',
minHeight: '300px',
backgroundColor: '#e0e0e0'
}}
>
{!this.state.streetViewReady && (
<div style={{ padding: '20px', textAlign: 'center' }}>
<Spinner /><span>{LocaleUtils.tr("streetview.loading")}</span>
</div>
)}
</div>
</ResizeableWindow>
);
}
return null;
}
}

export default connect((state) => ({
task: state.task.id,
map: state.map
}), {
setCurrentTask: require('../actions/task').setCurrentTask
})(StreetView);
9 changes: 8 additions & 1 deletion static/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,12 @@
{
"name": "Routing"
},
{
"name":"StreetView",
"cfg": {
"googleMapsApiKey": "***YOUR GOOGLE MAPS API KEY GOES HERE***"
}
},
{
"name": "TopBar",
"cfg": {
Expand Down Expand Up @@ -552,7 +558,8 @@
{"key": "Measure", "mode": "LineString", "icon": "measure_line"},
{"key": "Measure", "mode": "Polygon", "icon": "measure_polygon"},
{"key": "Print", "icon": "print"},
{"key": "Identify", "icon": "identify_region", "mode": "Region"}
{"key": "Identify", "icon": "identify_region", "mode": "Region"},
{"key":"StreetView", "task":"StreetView","icon":"cyclomedia"}
]
}
}
Expand Down
4 changes: 4 additions & 0 deletions static/translations/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,16 @@
"PrintScreen3D": "Raster Export",
"Redlining": "Redlining",
"Routing": "Routing",
"StreetView":"Google Street View",
"Tools": "Map Tools",
"TourGuide": "Tour Guide",
"TimeManager": "Time Manager",
"View3D": "3D View"
},
"menulabel": "Map & Tools"
},
"streetview": {
"loading":"Loading StreetView..."
},
"attribtable": {
"addfeature": "Add feature",
Expand Down
4 changes: 4 additions & 0 deletions static/translations/es-ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"Settings": "Opciones",
"Settings3D": "",
"Share": "Compartir enlace",
"StreetView":"Google Street View",
"ThemeSwitcher": "Tema",
"AttributeTable": "Tabla de Atributos",
"AuthenticationLogin": "Inicio de sesión",
Expand Down Expand Up @@ -62,6 +63,9 @@
},
"menulabel": "Mapa & Herramientas"
},
"streetview": {
"loading":"Cargando StreetView..."
},
"attribtable": {
"addfeature": "Añadir elemento",
"commit": "Commit de la fila modificada",
Expand Down
Loading
Loading