2021-09-24 20:56:48 +00:00
|
|
|
import React from 'react';
|
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
import AssetDownloads from '../classes/AssetDownloads';
|
|
|
|
import '../css/GCPPopup.scss';
|
|
|
|
import { _ } from '../classes/gettext';
|
|
|
|
|
|
|
|
class GCPPopup extends React.Component {
|
|
|
|
static propTypes = {
|
|
|
|
feature: PropTypes.object.isRequired,
|
|
|
|
task: PropTypes.object.isRequired,
|
|
|
|
};
|
|
|
|
|
|
|
|
constructor(props){
|
|
|
|
super(props);
|
|
|
|
|
|
|
|
this.state = {
|
|
|
|
error: "",
|
|
|
|
loading: true,
|
|
|
|
expandGCPImage: false,
|
|
|
|
selectedShot: "",
|
2021-09-25 16:58:35 +00:00
|
|
|
zoom: 4
|
2021-09-24 20:56:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
selectShot = (shotId) => {
|
|
|
|
if (shotId !== this.state.selectedShot){
|
|
|
|
this.setState({loading: true, selectedShot: shotId, error: ""});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_getCoords = (shotId, key) => {
|
|
|
|
if (!shotId) return [0.5, 0.5];
|
|
|
|
|
|
|
|
const { feature } = this.props;
|
|
|
|
const ob = feature.properties.observations.find(o => o.shot_id === shotId);
|
|
|
|
|
|
|
|
if (ob){
|
|
|
|
return ob[key];
|
|
|
|
}else{
|
|
|
|
return [0.5, 0.5];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getAnnotationCoords = (shotId) => {
|
|
|
|
return this._getCoords(shotId, 'annotated');
|
|
|
|
}
|
|
|
|
|
|
|
|
getReprojectedCoords = (shotId) => {
|
|
|
|
return this._getCoords(shotId, 'reprojected');
|
|
|
|
}
|
|
|
|
|
|
|
|
getThumbUrl = (size) => {
|
|
|
|
const { task } = this.props;
|
|
|
|
const { selectedShot, zoom } = this.state;
|
|
|
|
const annotated = this.getAnnotationCoords(selectedShot);
|
|
|
|
const reprojected = this.getReprojectedCoords(selectedShot);
|
|
|
|
|
2021-09-25 16:58:35 +00:00
|
|
|
return `/api/projects/${task.project}/tasks/${task.id}/images/thumbnail/${selectedShot}?size=${size}¢er_x=${annotated[0]}¢er_y=${annotated[1]}&draw_point=${annotated[0]},${annotated[1]}&point_color=f29900&point_radius=2&draw_point=${reprojected[0]},${reprojected[1]}&&point_color=00ff00&point_radius=2&zoom=${zoom}`;
|
2021-09-24 20:56:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
componentDidMount(){
|
|
|
|
const { feature } = this.props;
|
|
|
|
|
2021-09-24 21:30:22 +00:00
|
|
|
document.addEventListener("fullscreenchange", this.onFullscreenChange);
|
2021-09-24 20:56:48 +00:00
|
|
|
if (feature.properties.observations) this.selectShot(feature.properties.observations[0].shot_id);
|
2021-09-25 16:13:03 +00:00
|
|
|
if (this.imageContainer) this.imageContainer.addEventListener("mousewheel", this.onImageWheel); // onWheel doesn't work :/
|
2021-09-24 20:56:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount(){
|
2021-09-24 21:30:22 +00:00
|
|
|
document.removeEventListener("fullscreenchange", this.onFullscreenChange);
|
2021-09-25 16:13:03 +00:00
|
|
|
if (this.imageContainer) this.imageContainer.removeEventListener("mousewheel", this.onImageWheel);
|
2021-09-24 20:56:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
onFullscreenChange = (e) => {
|
|
|
|
if (!document.fullscreenElement){
|
|
|
|
this.setState({expandGCPImage: false});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
imageOnError = () => {
|
|
|
|
this.setState({error: _("Image missing"), loading: false});
|
|
|
|
}
|
|
|
|
|
|
|
|
imageOnLoad = () => {
|
|
|
|
this.setState({loading: false});
|
|
|
|
}
|
|
|
|
|
2021-09-25 16:13:03 +00:00
|
|
|
canZoomIn = () => {
|
2021-09-25 16:58:35 +00:00
|
|
|
return this.state.zoom < 10 && !this.state.loading;
|
2021-09-25 16:13:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
canZoomOut = () => {
|
|
|
|
return this.state.zoom > 1 && !this.state.loading;
|
|
|
|
}
|
|
|
|
|
|
|
|
zoomIn = () => {
|
|
|
|
if (this.canZoomIn()){
|
|
|
|
this.setState({loading: true, zoom: this.state.zoom + 1});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
zoomOut = () => {
|
|
|
|
if (this.canZoomOut()){
|
|
|
|
this.setState({loading: true, zoom: this.state.zoom - 1});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
onImageWheel = e => {
|
|
|
|
if (e.deltaY > 0){
|
|
|
|
this.zoomIn();
|
|
|
|
}else{
|
|
|
|
this.zoomOut();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-24 20:56:48 +00:00
|
|
|
onImgClick = () => {
|
|
|
|
const { expandGCPImage } = this.state;
|
|
|
|
|
|
|
|
if (!expandGCPImage){
|
|
|
|
this.image.requestFullscreen();
|
|
|
|
this.setState({ loading: true, expandGCPImage: true});
|
|
|
|
}else{
|
|
|
|
document.exitFullscreen();
|
2021-09-24 21:30:22 +00:00
|
|
|
this.setState({ loading: true, expandGCPImage: false });
|
2021-09-24 20:56:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
render(){
|
2021-09-26 17:01:22 +00:00
|
|
|
const { error, loading, expandGCPImage, selectedShot } = this.state;
|
2021-09-24 20:56:48 +00:00
|
|
|
const { feature, task } = this.props;
|
|
|
|
|
|
|
|
const downloadGCPLink = `/api/projects/${task.project}/tasks/${task.id}/download/ground_control_points.geojson`;
|
|
|
|
const assetDownload = AssetDownloads.only(["ground_control_points.geojson"])[0];
|
|
|
|
const imageUrl = expandGCPImage ? this.getThumbUrl(999999999) : this.getThumbUrl(320);
|
|
|
|
|
|
|
|
const shotLinks = [];
|
|
|
|
for (let i = 0; i < feature.properties.observations.length; i++){
|
|
|
|
const obs = feature.properties.observations[i];
|
|
|
|
if (obs.shot_id === selectedShot){
|
|
|
|
shotLinks.push(<span key={obs.shot_id}>{obs.shot_id}</span>);
|
|
|
|
}else{
|
|
|
|
shotLinks.push(<a key={obs.shot_id} className="gcp-image-link" href="javascript:void(0)" onClick={() => this.selectShot(obs.shot_id)}>{obs.shot_id}</a>);
|
|
|
|
}
|
|
|
|
if (i+1 < feature.properties.observations.length) shotLinks.push(<span key={"divider-" + i}> | </span>);
|
|
|
|
}
|
|
|
|
|
|
|
|
const imgStyle = {
|
2021-09-24 21:30:22 +00:00
|
|
|
borderRadius: "4px",
|
|
|
|
minHeight: "32px"
|
2021-09-24 20:56:48 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
return (<div className="gcp-popup">
|
|
|
|
<div className="title" title={feature.properties.id}>{feature.properties.id}</div>
|
|
|
|
<div>
|
|
|
|
{shotLinks}
|
|
|
|
</div>
|
|
|
|
|
2021-09-25 16:13:03 +00:00
|
|
|
<div className="image-container" ref={(domNode) => this.imageContainer = domNode }>
|
2021-09-24 20:56:48 +00:00
|
|
|
{loading ? <div className="spinner"><i className="fa fa-circle-notch fa-spin fa-fw"></i></div> : ""}
|
|
|
|
{error ? <div style={{marginTop: "8px"}}>{error}</div> : ""}
|
|
|
|
|
|
|
|
{!error && selectedShot !== "" ?
|
|
|
|
<div className={`image ${expandGCPImage ? "fullscreen" : ""} ${loading ? "loading" : ""}`} style={{marginTop: "8px"}} ref={(domNode) => { this.image = domNode;}}>
|
|
|
|
{loading && expandGCPImage ? <div><i className="fa fa-circle-notch fa-spin fa-fw"></i></div> : ""}
|
|
|
|
<a onClick={this.onImgClick} href="javascript:void(0);" title={selectedShot}><img style={imgStyle} src={imageUrl} onLoad={this.imageOnLoad} onError={this.imageOnError} /></a>
|
|
|
|
</div> : ""}
|
|
|
|
</div>
|
|
|
|
|
2021-09-25 16:21:23 +00:00
|
|
|
<div className="btn-group zoom-buttons">
|
|
|
|
<button onClick={this.zoomOut} disabled={!this.canZoomOut()} type="button" className="btn btn-xs btn-default" title="-">-</button>
|
|
|
|
<button onClick={this.zoomIn} disabled={!this.canZoomIn()} type="button" className="btn btn-xs btn-default" title="+">+</button>
|
|
|
|
</div>
|
2021-09-24 20:56:48 +00:00
|
|
|
|
|
|
|
<div>
|
|
|
|
<strong>{_("Horizontal error:")}</strong> {Math.abs(Math.max(feature.properties.error[0], feature.properties.error[1])).toFixed(3)} {_("(meters)")}
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<strong>{_("Vertical error:")}</strong> {Math.abs(feature.properties.error[2]).toFixed(3)} {_("(meters)")}
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<a href={downloadGCPLink}><i className={assetDownload.icon}></i> {assetDownload.label} </a>
|
|
|
|
</div>
|
|
|
|
</div>);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default GCPPopup;
|