0

我正在尝试建立一个 React 项目,我想在其中使用 ol-ext 提供的一些工具。我环顾四周,从库的创建者那里找到了一些 Codesandbox 项目,但由于在项目中导入库的方式导致错误,我无法使它们正常工作。

ol-ext 和其他 Open Layers 版本的语法或不兼容是否存在问题?

4

1 回答 1

1

对于遇到同样问题的任何人,我都设法让它工作,并实现了交互转换功能示例。下面是代码:

JS:

// Import stylesheets
import './style.css';
import "ol/ol.css";
import "ol-ext/dist/ol-ext.css";

import Transform from "ol-ext/interaction/Transform";
import Stamen from 'ol/source/Stamen';
import { Map, View } from "ol";
import { defaults } from "ol/control";
import * as olEvents from 'ol/events';
import TileLayer from 'ol/layer/Tile';
import { Style, Fill, Text, Icon, Stroke, RegularShape } from "ol/style";
import {Polygon, LineString, Point, Circle} from 'ol/geom';
import VectorLayer from "ol/layer/Vector";
import VectorSource from "ol/source/Vector";
import Feature from "ol/Feature";


 var interaction = new Transform ({
      enableRotatedTransform: false,
      /* Limit interaction inside bbox * /
      condition: function(e, features) {
        return ol.extent.containsXY([-465960, 5536486, 1001630, 6514880], e.coordinate[0], e.coordinate[1]);
      },
      /* */
      addCondition: olEvents.condition,
      // filter: function(f,l) { return f.getGeometry().getType()==='Polygon'; },
      // layers: [vector],
      hitTolerance: 2,
       translateFeature: false,
       scale: true,
       rotate: true,
       translate: true,
       stretch: true
    });

var circle = new RegularShape({
          fill: new Fill({color:[255,255,255,0.01]}),
          stroke: new Stroke({width:1, color:[0,0,0,0.01]}),
          radius: 8,
          points: 10
        });
        interaction.setStyle ('rotate',
          new Style({
            text: new Text ({
              text:'\uf0e2', 
              font:"16px Fontawesome",
              textAlign: "left",
              fill:new Fill({color:'red'})
            }),
            image: circle
          }));
        // Center of rotation
        interaction.setStyle ('rotate0',
          new Style({
            text: new Text ({
              text:'\uf0e2', 
              font:"20px Fontawesome",
              fill: new Fill({ color:[255,255,255,0.8] }),
              stroke: new Stroke({ width:2, color:'red' })
            }),
          }));
        // Style the move handle
        interaction.setStyle('translate',
          new Style({
            text: new Text ({
              text:'\uf047', 
              font:"20px Fontawesome", 
              fill: new Fill({ color:[255,255,255,0.8] }),
              stroke: new Stroke({ width:2, color:'red' })
            })
          }));



    // Layers
    var layers = [
      new TileLayer({ 
        title:'terrain-background',
        source: new Stamen({ layer: 'terrain' })
      })
    ]
    // The map
    var map = new Map({
      target: null,
        view: new View({
          zoom: 5,
          center: [261720, 5951081]
        }),
        controls: defaults({ "attribution": false }),
        layers: layers
      });

      // Style
    function getStyle(feature) {
      return [ new Style({
        image: new RegularShape({
          fill: new Fill({ color: [0,0,255,0.4]}),
          stroke: new Stroke({color: [0,0,255,1],width: 1}),
          radius: 10,
          points: 3,
          angle: feature.get('angle')||0
        }),
        fill: new Fill({color: [0,0,255,0.4]}),
        stroke: new Stroke({color: [0,0,255,1],width: 1})
      })];
    }

    // New vector layer
    var vector = new VectorLayer({
      name: 'Vecteur',
      source: new VectorSource({ wrapX: false }),
      style: getStyle
    })
    map.addLayer(vector);
    vector.getSource().addFeature(new Feature(new Polygon([[[34243, 6305749], [-288626, 5757848], [210354, 5576845], [300000, 6000000], [34243, 6305749]]])));
    vector.getSource().addFeature(new Feature(new LineString([[406033, 5664901], [689767, 5718712], [699551, 6149206], [425601, 6183449]])));
    vector.getSource().addFeature(new Feature(new Point(    [269914, 6248592])));
    vector.getSource().addFeature(new Feature(new Circle( [500000, 6400000], 100000 )));

    // Set cursor style
    Transform.prototype.Cursors['rotate'] = 'url(\'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAXAgMAAACdRDwzAAAAAXNSR0IArs4c6QAAAAlQTFRF////////AAAAjvTD7AAAAAF0Uk5TAEDm2GYAAAABYktHRACIBR1IAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH2wwSEgUFmXJDjQAAAEZJREFUCNdjYMAOuCCk6goQpbp0GpRSAFKcqdNmQKgIILUoNAxIMUWFhoKosNDQBKDgVAilCqcaQBogFFNoGNjsqSgUTgAAM3ES8k912EAAAAAASUVORK5CYII=\') 5 5, auto';
    Transform.prototype.Cursors['rotate0'] = 'url(\'data:image/png;base64,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\') 5 5, auto';

 map.addInteraction(interaction);


class App extends Component {
    state = {
      name: 'React',
      styles: false,
      scale: true,
      stretch: true,
      disableStretch: false,
      rotate: true,
      translate: true,
      translateFeature: false,
      forceMatching: false,
      info: "Hello there"
    };


  setHandleStyleInput = (event) => {
      this.setState({styles: event.target.checked})
    if (!interaction instanceof Transform) return;
      if (event.target.checked) {
        // Style the rotate handle
        var circle = new RegularShape({
          fill: new Fill({color:[255,255,255,0.01]}),
          stroke: new Stroke({width:1, color:[0,0,0,0.01]}),
          radius: 8,
          points: 10
        });
        interaction.setStyle ('rotate',
          new Style({
            text: new Text ({
              text:'\uf0e2', 
              font:"16px Fontawesome",
              textAlign: "left",
              fill:new Fill({color:'yellow'})
            }),
            image: circle
          }));
        // Center of rotation
        interaction.setStyle ('rotate0',
          new Style({
            text: new Text ({
              text:'\uf0e2', 
              font:"20px Fontawesome",
              fill: new Fill({ color:[255,255,255,0.8] }),
              stroke: new Stroke({ width:2, color:'yellow' })
            }),
          }));
        // Style the move handle
        interaction.setStyle('translate',
          new Style({
            text: new Text ({
              text:'\uf047', 
              font:"20px Fontawesome", 
              fill: new Fill({ color:[255,255,255,0.8] }),
              stroke: new Stroke({ width:2, color:'yellow' })
            })
          }));
          interaction.setStyle ('scaleh1', 
          new Style({
            text: new Text ({
            text:'\uf07d', 
            font:"bold 20px Fontawesome", 
            fill: new Fill({ color:[255,255,255,0.8] }),
            stroke: new Stroke({ width:2, color:'yellow' })
          })
        }));
        interaction.style.scaleh3 = interaction.style.scaleh1;
        interaction.setStyle('scalev',
          new Style({
            text: new Text ({
              text:'\uf07e', 
              font:"bold 20px Fontawesome", 
              fill: new Fill({ color:[255,255,255,0.8] }),
              stroke: new Stroke({ width:2, color:'yellow' })
            })
          }));
        interaction.style.scalev2 = interaction.style.scalev;
      } else {
        interaction.setDefaultStyle();
      }
      // Refresh
      interaction.set('translate', interaction.get('translate'));


  }

    setPropertieScale = (p) =>{

      this.setState({scale: p.target.checked})
      if (p.target.checked) this.setState({ disableStretch: false});
      else this.setState({ disableStretch: true});
    }

    setPropertieStretch = (p) =>{
      this.setState({stretch: p.target.checked})
    }

    setPropertieRotate = (p) =>{
      this.setState({rotate: p.target.checked})
    }

    setPropertieTranslate = (p) =>{
      this.setState({translate: p.target.checked})
    }
    setPropertieTranslateFeature = (p) =>{
      this.setState({translateFeature: p.target.checked})
    }

    componentDidMount() {


    map.setTarget("map");
    }

  componentDidUpdate(prevState) {
    /** Style the transform handles for the current interaction
    */
    if(this.state.scale !== prevState.scale || this.state.stretch !== prevState.stretch || this.state.rotate !== prevState.rotate || this.state.translate !== prevState.translate || this.state.styles !== prevState.styles){


    var interaction = new Transform ({
      enableRotatedTransform: false,
      /* Limit interaction inside bbox * /
      condition: function(e, features) {
        return ol.extent.containsXY([-465960, 5536486, 1001630, 6514880], e.coordinate[0], e.coordinate[1]);
      },
      /* */
      addCondition: olEvents.condition,
      // filter: function(f,l) { return f.getGeometry().getType()==='Polygon'; },
      // layers: [vector],
      hitTolerance: 2,
       translateFeature: this.state.translateFeature,
       scale: this.state.scale,
       rotate: this.state.rotate,
       translate: this.state.translate,
       stretch: this.state.stretch
    });
    interaction.set('translate', interaction.get('translate'));
    // Style handles

    }
  }

  render() {

    var info = "Heyyou"

     // Events handlers
    var startangle = 0;
    var d=[0,0];

    // Handle rotate on first point
    var firstPoint = false;
    interaction.on (['select'], function(e) {
      if (firstPoint && e.features && e.features.getLength()) {
        interaction.setCenter(e.features.getArray()[0].getGeometry().getFirstCoordinate());
      }
    });

    interaction.on (['rotatestart','translatestart'], function(e){
      // Rotation
      startangle = e.feature.get('angle')||0;
      // Translation
      d=[0,0];
    });
    interaction.on('rotating', (e)=>{
      info= "rotate: "+((e.angle*180/Math.PI -180)%360+180).toFixed(2)
      // Set angle attribute to be used on style !
      e.feature.set('angle', startangle - e.angle);
    });
    interaction.on('translating', (e) =>{

      d[0]+=e.delta[0];
      d[1]+=e.delta[1];
      info= "translate: "+d[0].toFixed(2)+","+d[1].toFixed(2)


      if (firstPoint) {
        interaction.setCenter(e.features.getArray()[0].getGeometry().getFirstCoordinate());
      }
    });
    interaction.on('scaling', (e) => {
      info= "scale: "+e.scale[0].toFixed(2)+","+e.scale[1].toFixed(2)

      if (firstPoint) {
        interaction.setCenter(e.features.getArray()[0].getGeometry().getFirstCoordinate());
      }
    });
    interaction.on(['rotateend', 'translateend', 'scaleend'], (e) =>{
      this.setState({info: info})
      info= ""

    });

    return (
      <div>
        <div id="map" style={{width:"500px", height:"400px"}}></div>

  <div className="options" >
    <h2>Options:</h2>
    <ul><li>

      <input
            className="style"
            type="checkbox"
            checked={this.state.styles}
            onChange={this.setHandleStyleInput} />
      <label> styles transform handles (using fontawesome)</label>
    </li><li>
    <input
            className="scale"
            type="checkbox"
            checked={this.state.scale}
            onChange={this.setPropertieScale} /><label> enable scale</label>
    </li><li>
    <input
            className="stretch"
            type="checkbox"
            checked={this.state.stretch}
            onChange={this.setPropertieStretch} disabled={this.state.disableStretch} />
      <label> enable stretch</label>
    </li><li>
    <input
            className="rotate"
            type="checkbox"
            checked={this.state.rotate}
            onChange={this.setPropertieRotate} />
      <label> enable rotate</label>
    </li><li>
      <input
            className="translate"
            type="checkbox"
            checked={this.state.translate}
            onChange={this.setPropertieTranslate} />
      <label> enable translate</label>
    </li><li>
    <input
            className="translateFeature"
            type="checkbox"
            checked={this.state.translateFeature}
            onChange={this.setPropertieTranslateFeature} />
      <label> translate when click on feature</label>
    </li><li>
      SetRotateCenter:
      <button onClick={()=>{firstPoint=false; interaction.setCenter()}}>objects</button>
      <button onClick={()=>{firstPoint=false; interaction.setCenter(map.getView().getCenter())}}>view center</button>
      <button onClick={()=>{firstPoint=true;}}>first point</button>
    </li><li>
      <hr/>
      Use <i>Shift</i> to add object to tranform
      <hr/>
      Use <i>Shift</i> key to preserve proportions when scaling (see keepAspectRatio).
      <br />
      Use <i>Ctrl</i> key to modify the center when scaling.
    </li></ul>
    <div style={{background:"white", padding:"0 0.45em"}}><span id="info"></span>{this.state.info}</div>
  </div>
      </div>
    );
  }
}

HTML:

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">

<div id="root"></div>

CSS:

h1, p {
  font-family: Lato;
}

a.icss-github-corner,
a.icss-github-corner-left {
  font-size: 2.5em;
  position: fixed;
  top:0;
  right: 0;
  z-index: 1000;
  color: #fff;
  background-color: #333;
  padding: .5em 2em 0;
  text-align: center;
  -webkit-transform: rotate(45deg);
      -ms-transform: rotate(45deg);
          transform: rotate(45deg);
  -webkit-transform-origin: 3.9em 3em;
      -ms-transform-origin: 3.9em 3em;
          transform-origin: 3.9em 3em;
  overflow: hidden;
}
a.icss-github-corner-left {
  left: 0;
  right: auto;
  -webkit-transform: rotate(-45deg);
      -ms-transform: rotate(-45deg);
          transform: rotate(-45deg);
  -webkit-transform-origin: 1em 3.1em;
      -ms-transform-origin: 1em 3.1em;
          transform-origin: 1em 3.1em;
}
a.icss-github-corner:hover i,
a.icss-github-corner-left:hover i{
  -webkit-animation: vertical 2s ease;
          animation: vertical 2s ease;
}

a.icss-github-corner i,
a.icss-github-corner-left i,
i.icss-github-corner {
  color: #fff;
  position: relative;
  display:inline-block;
  font-style: normal;
  background-color:currentColor;
  -webkit-box-sizing: border-box;
          box-sizing: border-box;
  vertical-align: middle;
  width: .8em;
  height: .6em;
  -webkit-border-radius: 45% 45% 45% 45% / 50%;
          border-radius: 45% 45% 45% 45% / 50%;
  background-color: currentColor;
  -webkit-box-shadow: 0 .35em 0 -.2em, 
      0 .38em 0 -.2em,
      0 .41em 0 -.2em,
      0 .44em 0 -.2em,
      0 .47em 0 -.2em;
          box-shadow: 0 .35em 0 -.2em, 
      0 .38em 0 -.2em,
      0 .41em 0 -.2em,
      0 .44em 0 -.2em,
      0 .47em 0 -.2em;
  margin: .12em .1em .23em;
}
a.icss-github-corner i:before,
a.icss-github-corner-left i:before,
i.icss-github-corner:before {
  content: "";
  border-width: 0; 
  position: absolute;
  -webkit-box-sizing: border-box;
          box-sizing: border-box;
  border-width: .15em .15em;
  border-style: solid;
  -webkit-border-radius: 0.02em 60% 100% 80%;
          border-radius: 0.02em 60% 100% 80%;
  left: 0;
  top: -.07em;
  -webkit-transform: rotate(20deg);
      -ms-transform: rotate(20deg);
          transform: rotate(20deg);
}
a.icss-github-corner i:after,
a.icss-github-corner-left i:after,
i.icss-github-corner:after {
  content: "";
  border-width: 0; 
  position: absolute;
  -webkit-box-sizing: border-box;
          box-sizing: border-box;
  border-width: .15em .15em;
  border-style: solid;
  -webkit-border-radius: 0.02em 80% 100% 60%;
          border-radius: 0.02em 80% 100% 60%;
  left: .5em;
  top: -.07em;
  -webkit-transform: rotate(65deg);
      -ms-transform: rotate(65deg);
          transform: rotate(65deg);
}
@-webkit-keyframes vertical {
    0%{-webkit-transform:translate(0,-3px);transform:translate(0,-3px)}
    4%{-webkit-transform:translate(0,3px);transform:translate(0,3px)}
    8%{-webkit-transform:translate(0,-3px);transform:translate(0,-3px)}
    12%{-webkit-transform:translate(0,3px);transform:translate(0,3px)}
    16%{-webkit-transform:translate(0,-3px);transform:translate(0,-3px)}
    20%{-webkit-transform:translate(0,3px);transform:translate(0,3px)}
    22%,100%{-webkit-transform:translate(0,0);transform:translate(0,0)}
}
@keyframes vertical {
    0%{-webkit-transform:translate(0,-3px);transform:translate(0,-3px)}
    4%{-webkit-transform:translate(0,3px);transform:translate(0,3px)}
    8%{-webkit-transform:translate(0,-3px);transform:translate(0,-3px)}
    12%{-webkit-transform:translate(0,3px);transform:translate(0,3px)}
    16%{-webkit-transform:translate(0,-3px);transform:translate(0,-3px)}
    20%{-webkit-transform:translate(0,3px);transform:translate(0,3px)}
    22%,100%{-webkit-transform:translate(0,0);transform:translate(0,0)}
}
/**/
body {
  font-family: 'Lucida Grande',Verdana,Geneva,Lucida,Arial,Helvetica,sans-serif;
    font-size: 16px;
}
a, i, b {
  color: #337ab7;
    text-decoration: none;
}
button i {
  color: #fff;
}
.ol-control.ol-bar .ol-control button i {
  color: #fff;
}
a:hover {
  text-decoration: underline;
}
a.title {
  text-decoration: none;
}
h1 {
  background: #1f6b75 url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAACE1BMVEX///8A//8AgICA//8AVVVAQID///8rVVVJtttgv98nTmJ2xNgkW1ttyNsmWWZmzNZYxM4gWGgeU2JmzNNr0N1Rwc0eU2VXxdEhV2JqytQeVmMhVmNoydUfVGUgVGQfVGQfVmVqy9hqy9dWw9AfVWRpydVry9YhVmMgVGNUw9BrytchVWRexdGw294gVWQgVmUhVWPd4N6HoaZsy9cfVmQgVGRrytZsy9cgVWQgVWMgVWRsy9YfVWNsy9YgVWVty9YgVWVry9UgVWRsy9Zsy9UfVWRsy9YgVWVty9YgVWRty9Vsy9aM09sgVWRTws/AzM0gVWRtzNYgVWRuy9Zsy9cgVWRGcHxty9bb5ORbxdEgVWRty9bn6OZTws9mydRfxtLX3Nva5eRix9NFcXxOd4JPeINQeIMiVmVUws9Vws9Vw9BXw9BYxNBaxNBbxNBcxdJexdElWWgmWmhjyNRlx9IqXGtoipNpytVqytVryNNrytZsjZUuX210k5t1y9R2zNR3y9V4lp57zth9zdaAnKOGoaeK0NiNpquV09mesrag1tuitbmj1tuj19uktrqr2d2svcCu2d2xwMO63N+7x8nA3uDC3uDFz9DK4eHL4eLN4eIyYnDX5OM5Z3Tb397e4uDf4uHf5uXi5ePi5+Xj5+Xk5+Xm5+Xm6OY6aHXQ19fT4+NfhI1Ww89gx9Nhx9Nsy9ZWw9Dpj2abAAAAWnRSTlMAAQICAwQEBgcIDQ0ODhQZGiAiIyYpKywvNTs+QklPUlNUWWJjaGt0dnd+hIWFh4mNjZCSm6CpsbW2t7nDzNDT1dje5efr7PHy9PT29/j4+Pn5+vr8/f39/f6DPtKwAAABTklEQVR4Xr3QVWPbMBSAUTVFZmZmhhSXMjNvkhwqMzMzMzPDeD+xASvObKePPa+ffHVl8PlsnE0+qPpBuQjVJjno6pZpSKXYl7/bZyFaQxhf98hHDKEppwdWIW1frFnrxSOWHFfWesSEWC6R/P4zOFrix3TzDFLlXRTR8c0fEEJ1/itpo7SVO9Jdr1DVxZ0USyjZsEY5vZfiiAC0UoTGOrm9PZLuRl8X+Dq1HQtoFbJZbv61i+Poblh/97TC7n0neCcK0ETNUrz1/xPHf+DNAW9Ac6t8O8WH3Vp98f5lCaYKAOFZMLyHL4Y0fe319idMNgMMp+zWVSybUed/+/h7I4wRAG1W6XDy4XmjR9HnzvDRZXUAYDFOhC1S/Hh+fIXxen+eO+AKqbs+wAo30zDTDvDxKoJN88sjUzDFAvBzEUGFsnADoIvAJzoh2BZ8sner+Ke/vwECuQAAAABJRU5ErkJggg==") no-repeat scroll 10px center;
    color: #fff;
    font-size: 1.5em;
    padding: 0.5em 50px;
    margin:0;
}
h2 {
  color: #337ab7;
    font-size:1.1em;
    margin: 0.5em 0;
}

.info {
  background:#f5f5f5;
    padding:0.5em;
    margin: 1em 0;
}
.info ul {
  margin:0;
}

#map {
  float:left;
    margin-right:1em;
    background:#ddd;
}

.ol-attribution img {
  vertical-align:middle;
}

.layerSwitcher {
  display:inline-block;
    background:#cdf;
    padding:0.5em;
}
.btn {
  color:#fff;
    background:#369;
    padding:0.5em;
    text-decoration:none;
    cursor:pointer;
    display:inline-block;
    margin-right:0.5em;
}

.block,
.options {
  display: table;
    margin: 0.5em;
    position: relative;
    z-index: 1;
    margin:1em;
}
.options {
  background: #def;
    padding: 0.5em;
}
.options ul {
  list-style: none;
    padding-left: 0.5em;
}

i[class*="icss-"] {
  position: relative;
  display:inline-block;
  font-style: normal;
  background-color:currentColor;
  box-sizing: border-box;
  vertical-align: middle;
  font-size: 1.5em;
}
i[class*="icss-"]:before, 
i[class*="icss-"]:after {
  content: "";
  border-width: 0; 
  position: absolute;
}
i.icss-book {
  width:1em;
  height:.8em;
  background-color: transparent; 
  margin: 0 .03em .08em 0;
}
i.icss-book:before {
  height: .8em;
  width: 0.7em;
  box-shadow: inset 0 0 0 0.15em, 
      inset 0 -.48em,
      .07em .07em;
  border: 0.07em solid transparent;
  border-width: 0 0.07em .07em 0;
  border-radius: .05em .15em .1em .1em / .05em .05em .1em .05em;
  transform: skewX(-20deg);
  left: 0.15em;
}
i.icss-book:after {
  width: .2em;
  height: .2em;
  background-color: transparent;
  border: 0.05em solid currentColor;
  border-color: currentColor transparent transparent currentColor;
  border-radius: 50%;
  transform: rotate(-45deg);
  top: 0.67em;
  left: .018em;
  box-shadow: .13em -.15em 0 -.05em,
      .51em -.33em 0 -.05em;
}
.experimental {
  color:#fff; 
  background: #f91; 
  padding:.2em .5em;
  display: inline-block;
  -webkit-transform: rotate(-5deg);
  transform: rotate(-5deg);
  margin: -1em 0;
}

.ol-attribution ul {
  font-size: .8em;
}

这里还有一个工作示例:StackBlitz

于 2020-06-16T13:55:36.207 回答