kopia lustrzana https://github.com/OpenDroneMap/WebODM
Add GCPPopup
rodzic
afa064ac20
commit
49072d58b1
|
@ -0,0 +1,152 @@
|
|||
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: "",
|
||||
zoom: 1
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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=20&draw_point=${reprojected[0]},${reprojected[1]}&&point_color=00ff00&point_radius=20`;
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
const { feature } = this.props;
|
||||
|
||||
if (this.image) this.image.addEventListener("fullscreenchange", this.onFullscreenChange);
|
||||
if (feature.properties.observations) this.selectShot(feature.properties.observations[0].shot_id);
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
if (this.image) this.image.removeEventListener("fullscreenchange", this.onFullscreenChange);
|
||||
}
|
||||
|
||||
onFullscreenChange = (e) => {
|
||||
if (!document.fullscreenElement){
|
||||
this.setState({expandGCPImage: false});
|
||||
}
|
||||
}
|
||||
|
||||
imageOnError = () => {
|
||||
this.setState({error: _("Image missing"), loading: false});
|
||||
}
|
||||
|
||||
imageOnLoad = () => {
|
||||
this.setState({loading: false});
|
||||
}
|
||||
|
||||
onImgClick = () => {
|
||||
const { expandGCPImage } = this.state;
|
||||
|
||||
if (!expandGCPImage){
|
||||
this.image.requestFullscreen();
|
||||
this.setState({ loading: true, expandGCPImage: true});
|
||||
}else{
|
||||
document.exitFullscreen();
|
||||
this.setState({ expandGCPImage: false });
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
const { error, loading, expandGCPImage, selectedShot, zoom } = this.state;
|
||||
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 = {
|
||||
borderRadius: "4px"
|
||||
};
|
||||
|
||||
return (<div className="gcp-popup">
|
||||
<div className="title" title={feature.properties.id}>{feature.properties.id}</div>
|
||||
<div>
|
||||
{shotLinks}
|
||||
</div>
|
||||
|
||||
<div className="image-container">
|
||||
{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>
|
||||
|
||||
|
||||
<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;
|
|
@ -0,0 +1,52 @@
|
|||
.gcp-popup{
|
||||
div{
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
div.title{
|
||||
margin-top: 0;
|
||||
font-weight: bold;
|
||||
max-width: 300px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.image-container{
|
||||
position: relative;
|
||||
.spinner{
|
||||
position: absolute;
|
||||
left: 4px;
|
||||
min-height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
div.image{
|
||||
overflow: hidden;
|
||||
a{
|
||||
cursor: zoom-in;
|
||||
}
|
||||
&.loading{
|
||||
opacity: 0.2;
|
||||
}
|
||||
&.fullscreen{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
i{
|
||||
font-size: 200%;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 45%;
|
||||
}
|
||||
a{
|
||||
cursor: zoom-out;
|
||||
}
|
||||
img{
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Ładowanie…
Reference in New Issue