jsts + openLayer 解决几何自相交报错问题

报错:TopologyException: found non-noded intersection between LINESTRING ( 13022630.89845247 5279155.061076987, 13022019.402226187 5278543.81202375 ) and LINESTRING ( 13022546.663871666 5273866.515213738, 13022019.402226187 5286795.6742424425 ) [ (13022342.73927607, 5278867.01837748, NaN) ]

解决方法

        通过实例化jsts.gemo.GeometryFactory,根据坐标数据创建闭合线 , 通过isSimple方法判断其是否自相交。

        若其自相交则通过闭合线创建Polygon并获取其缓冲区数据 ,并获取类,若其类型为 Polygon则 获取当前缓冲区坐标数据 创建新的 Polygon返回, 若其类型为 MultiPolygon 则 遍历缓冲区每一个几何图形,然后通过union 方法将每一个几何图形融合为一个图形 并返回。

准备工作

引入jsts 和 openlayer 并实例化

import 'jsts/dist/jsts.min.js';
import {
  GeometryCollection,
  LinearRing,
  LineString,
  MultiLineString,
  MultiPoint,
  MultiPolygon,
  Point,
  Polygon,
} from 'ol/geom';
// 实例化OL解析类
const OLParser = new jsts.io.OL3Parser();

// 注入OL几何对象
OLParser.inject(
  Point,
  LineString,
  LinearRing,
  Polygon,
  MultiPoint,
  MultiLineString,
  MultiPolygon,
  GeometryCollection,
);

创建jsts几何处理函数

方法参考JSTS介绍和功能简单示例_jts api文档_GIS小虫的博客-CSDN博客

取交         

                ​​​​​​ 

  /**
   * 取交
   * @param geom
   * @param geomB
   * @returns
   */
  const intersects = (geom, geomB) => {
    const jstsGeom = OLParser.read(geom);
    const jstsGeomB = OLParser.read(geomB);

    if (!jstsGeom.isValid() || !jstsGeomB.isValid()) {
      console.error('几何对象出现拓扑错误,请检查修复');

      return null;
    }

    const difference = jstsGeom.intersection(jstsGeomB);

    return OLParser.write(difference);
  };

补集 

                ​​​​​​


  /**
   * 取geom中geomB的补集
   * @param geom
   * @param geomB
   * @returns
   */
  const getDifference = (geom, geomB) => {
    const jstsGeom = OLParser.read(geom);
    const jstsGeomB = OLParser.read(geomB);

    if (!jstsGeom.isValid() || !jstsGeomB.isValid()) {
      console.error('几何对象出现拓扑错误,请检查修复');

      return null;
    }

    const difference = jstsGeom.difference(jstsGeomB);

    return OLParser.write(difference);
  };

融合  
 /**
   * 融合
   * @param geom
   * @param geomB
   * @returns
   */
  const union = (geom, geomB) => {
    const jstsGeom = OLParser.read(geom);
    const jstsGeomB = OLParser.read(geomB);

    if (!jstsGeom.isValid() || !jstsGeomB.isValid()) {
      console.error('几何对象出现拓扑错误,请检查修复');

      return null;
    }

    const difference = jstsGeom.union(jstsGeomB);

    return OLParser.write(difference);
  };
对等差分

  /**
   * 对等差分
   * @param geom
   * @param geomB
   * @returns
   */
  const symDifference = (geom, geomB) => {
    const jstsGeom = OLParser.read(geom);
    const jstsGeomB = OLParser.read(geomB);

    if (!jstsGeom.isValid() || !jstsGeomB.isValid()) {
      console.error('几何对象出现拓扑错误,请检查修复');

      return null;
    }

    const difference = jstsGeom.symDifference(jstsGeomB);

    return OLParser.write(difference);
  };

创建解决自相交处理函数

  /**
   *  jsts相交处理
   *
   * @param {[number,number][]} coordinates
   * @return {Polygon|MultiPolygon}
   */
  function jstsIntersect(coordinates) {
    // 返回结果实例
    let mergeMultiPolygon;
    // 实例化几何操作工厂类
    let geomFactory = new jsts.geom.GeometryFactory();
    // 转换坐标Coordinate
    let jstsCoordinates = coordinates.map(function (pt) {
      return new jsts.geom.Coordinate(pt[0], pt[1]);
    });
    // 创建一个闭合线几何
    let linearRing = geomFactory.createLinearRing(jstsCoordinates);

    // 查看是否是一个简单几何, 是否自相交
    if (!linearRing.isSimple()) {
      //若要拆分它并查明它是否自相交,请使用缓冲区 buffer(0) 0 为缓冲区宽度

      let jstsPolygon = geomFactory.createPolygon(linearRing).buffer(0);

      if (jstsPolygon.getGeometryType() !== 'MultiPolygon') {
        // 获取闭合线几何数据创建面
        let _coordinates = jstsPolygon._shell._points._coordinates.map(
          function (pr) {
            return [pr.x, pr.y];
          },
        );

        // 这里目前限制为 Polygon
        mergeMultiPolygon = new Polygon([_coordinates]);
      } else {
        let list = [];

        // 遍历每个面 处理数据的结构
        for (let i = 0; i < jstsPolygon._geometries.length; i++) {
          let item = jstsPolygon._geometries[i];

          let p = item._shell._points._coordinates.map(function (pr) {
            return [pr.x, pr.y];
          });

          list.push(p);
        }

        // 合并融合每一个面 变成一个简单几何
        mergeMultiPolygon = list.reduce((accumulator, currentValue) => {
          if (accumulator) {
            return union(accumulator, new Polygon([currentValue]));
          } else {
            return new Polygon([currentValue]);
          }
        }, null);
      }
    }

    return mergeMultiPolygon;
  }

        

综合示例

import Fill from 'ol/style/Fill';
import Stroke from 'ol/style/Stroke';
import Style from 'ol/style/Style';
import React, { useRef } from 'react';
// import * as ol from 'ol';
import { Feature, Map, View } from 'ol';
import * as format from 'ol/format';
import * as olLayer from 'ol/layer';
// import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer';
import * as turf from '@turf/turf';
import * as geom from 'ol/geom';
import * as olSource from 'ol/source';
import { useEffect, useState } from 'react';
import styless from './index.module.less';
// import jsts from 'jsts';
import { message } from 'antd';
import 'jsts/dist/jsts.min.js';
import {
  GeometryCollection,
  LinearRing,
  LineString,
  MultiLineString,
  MultiPoint,
  MultiPolygon,
  Point,
  Polygon,
} from 'ol/geom';
import Draw from 'ol/interaction/Draw';
import 'ol/ol.css';

let integrationLayers, ilSource, lineString, sketch;

// 合并图形几何 对象
let mergeGeom;
// 实例化OL解析类
const OLParser = new jsts.io.OL3Parser();

// 注入OL几何对象
OLParser.inject(
  Point,
  LineString,
  LinearRing,
  Polygon,
  MultiPoint,
  MultiLineString,
  MultiPolygon,
  GeometryCollection,
);

const ModelSampling = () => {
  const mapRef = useRef();
  // const [isDraw, setIsDraw] = useState(false);
  const isDraw = useRef(false);
  const isDrag = useRef(false);
  const types1 = useRef();
  const [types, setTypes] = useState();

  function init() {
    const map = new Map({
      target: 'mapContainer',
      view: new View({
        center: [13090048.3574, 5182909.5998],
        zoom: 9,
        minZoom: 3,
        maxZoom: 20,
      }),
      layers: [
        new olLayer.Tile({
          source: new olSource.OSM(),
        }),
      ],
    });
    //
    const lineSource = new olSource.Vector();
    const vectorSource = new olSource.Vector();
    const lineVector = new olLayer.Vector({
      source: lineSource,
      style: new Style({
        stroke: new Stroke({
          color: 'red',
          width: 2,
        }),
        fill: new Fill({
          color: '#ff0',
          // width: 2,
        }),
      }),
    });

    ilSource = new olSource.Vector();

    integrationLayers = new olLayer.Vector({
      source: ilSource,
      style: new Style({
        stroke: new Stroke({
          color: 'red',
          width: 2,
        }),
        fill: new Fill({
          color: '#ff03',
          // width: 2,
        }),
      }),
    });

    map.addLayer(integrationLayers);
    map.addLayer(lineVector);

    let pointerMoveHandler = function (evt) {
      if (!sketch || !isDraw.current) return;

      if (evt.originalEvent.buttons === 4) {
        let coordinates = sketch.getGeometry().getCoordinates();

        coordinates.push(evt.coordinate);
        sketch.getGeometry().setCoordinates(coordinates);
      }
    };

    map.on('pointermove', pointerMoveHandler);

    map.on('pointerdown', function (evt) {
      // 鼠标中键
      if (evt.originalEvent.button === 1) {
        if (!sketch && isDraw.current) {
          sketch = new Feature(new geom.LineString([]));
          sketch.setId('paths');
          lineSource.addFeature(sketch);
        }
      }
    });

    map.on('dblclick', function (e) {
      if (!sketch) return;

      let coordinates = sketch.getGeometry().getCoordinates();
      coordinates.push(coordinates[0]);

      // let geomFactory = new jsts.geom.GeometryFactory();

      // // 转换坐标Coordinate
      // let jstsCoordinates = coordinates.map(function (pt) {
      //   return new jsts.geom.Coordinate(pt[0], pt[1]);
      // });

      // let linearRing = geomFactory.createLinearRing(jstsCoordinates);

      let mergeMultiPolygon = jstsIntersect(coordinates);
      console.log(mergeMultiPolygon, 'mergeMultiPolygon');
      if (mergeGeom) {
        let fn = types1.current === 1 ? union : getDifference;

        let _geom = fn(mergeGeom, mergeMultiPolygon);

        if (_geom) {
          mergeGeom = _geom;
          sketch = new Feature(_geom);
        } else {
          message.warning('绘画几何对象出现拓扑错误,请检查修复');
        }
      } else {
        // mergeGeom = new Polygon([coordinates]);
        console.log(mergeMultiPolygon, 'mergeMultiPolygon');
        mergeGeom = mergeMultiPolygon ?? new Polygon([coordinates]);

        let jstsGeom = OLParser.read(mergeGeom);

        sketch = new Feature(mergeGeom);

        if (!jstsGeom.isValid()) {
          message.warning('绘画几何对象出现拓扑错误,请检查修复');
          mergeGeom = null;
          sketch = null;
        }
      }

      // 清除 画线源
      lineSource.clear();
      // 清除结果 面
      ilSource.clear();

      ilSource.addFeature(new Feature(mergeGeom));

      let features = ilSource.getFeatures();
      // 创建一个 GeoJSON 格式的对象
      let geojsonFormat = new format.GeoJSON();

      // 将要素转换为 GeoJSON 字符串
      let geojsonStr = geojsonFormat.writeFeatures(features);

      console.log(turf.toWgs84(JSON.parse(geojsonStr)));
      sketch = null;
    });
    // drawClick(map, vectorSource);
    mapRef.current = map;
  }

  /**
   *  jsts相交处理
   *
   * @param {[number,number][]} coordinates
   * @return {Polygon|MultiPolygon}
   */
  function jstsIntersect(coordinates) {
    // 返回结果实例
    let mergeMultiPolygon = new Polygon([coordinates]);
    // 实例化几何操作工厂类
    let geomFactory = new jsts.geom.GeometryFactory();
    // 转换坐标Coordinate
    let jstsCoordinates = coordinates.map(function (pt) {
      return new jsts.geom.Coordinate(pt[0], pt[1]);
    });
    // 创建一个闭合线几何
    let linearRing = geomFactory.createLinearRing(jstsCoordinates);

    // 查看是否是一个简单几何, 是否自相交
    if (!linearRing.isSimple()) {
      //若要拆分它并查明它是否自相交,请使用缓冲区 buffer(0) 0 为缓冲区宽度

      let jstsPolygon = geomFactory.createPolygon(linearRing).buffer(0);

      if (jstsPolygon.getGeometryType() !== 'MultiPolygon') {
        // 获取闭合线几何数据创建面
        let _coordinates = jstsPolygon._shell._points._coordinates.map(
          function (pr) {
            return [pr.x, pr.y];
          },
        );

        // 这里目前限制为 Polygon
        mergeMultiPolygon = new Polygon([_coordinates]);
      } else {
        let list = [];

        // 遍历每个面 处理数据的结构
        for (let i = 0; i < jstsPolygon._geometries.length; i++) {
          let item = jstsPolygon._geometries[i];

          let p = item._shell._points._coordinates.map(function (pr) {
            return [pr.x, pr.y];
          });

          list.push(p);
        }

        // 合并融合每一个面 变成一个简单几何
        mergeMultiPolygon = list.reduce((accumulator, currentValue) => {
          if (accumulator) {
            return union(accumulator, new Polygon([currentValue]));
          } else {
            return new Polygon([currentValue]);
          }
        }, null);
      }
    }

    return mergeMultiPolygon;
  }
  // 点选绘制
  function drawClick(map, vectorSource) {
    // 创建一个绘制交互对象
    let draw = new Draw({
      source: vectorSource, // 矢量数据源
      type: 'Polygon', // 绘制类型为面
    });

    // 添加绘制交互到地图中
    map.addInteraction(draw);

    // 监听绘制完成事件
    draw.on('drawend', function (event) {
      let feature = event.feature; // 获取绘制的要素

      // 可以在这里对要素进行进一步处理,或者将其保存到数据库等操作
      console.log(feature, feature.getGeometry().getCoordinates(), 'feature');
      vectorSource.addFeature(feature);
      let vectors = new olLayer.Vector({
        source: vectorSource,
        style: new Style({
          stroke: new Stroke({
            color: 'red',
            width: 2,
          }),
          fill: new Fill({
            color: '#ff03',
            // width: 2,
          }),
        }),
      });

      map.addLayer(vectors);
      // 停止绘制交互
      map.removeInteraction(draw);
    });
  }

  /**
   * 取交
   * @param geom
   * @param geomB
   * @returns
   */
  const intersects = (geom, geomB) => {
    const jstsGeom = OLParser.read(geom);
    const jstsGeomB = OLParser.read(geomB);

    if (!jstsGeom.isValid() || !jstsGeomB.isValid()) {
      console.error('几何对象出现拓扑错误,请检查修复');

      return null;
    }

    const difference = jstsGeom.intersection(jstsGeomB);

    return OLParser.write(difference);
  };

  /**
   * 取geom中geomB的补集
   * @param geom
   * @param geomB
   * @returns
   */
  const getDifference = (geom, geomB) => {
    const jstsGeom = OLParser.read(geom);
    const jstsGeomB = OLParser.read(geomB);

    if (!jstsGeom.isValid() || !jstsGeomB.isValid()) {
      console.error('几何对象出现拓扑错误,请检查修复');

      return null;
    }

    const difference = jstsGeom.difference(jstsGeomB);

    return OLParser.write(difference);
  };

  /**
   * 融合
   * @param geom
   * @param geomB
   * @returns
   */
  const union = (geom, geomB) => {
    const jstsGeom = OLParser.read(geom);
    const jstsGeomB = OLParser.read(geomB);

    if (!jstsGeom.isValid() || !jstsGeomB.isValid()) {
      console.error('几何对象出现拓扑错误,请检查修复');

      return null;
    }

    const difference = jstsGeom.union(jstsGeomB);

    return OLParser.write(difference);
  };

  /**
   * 对等差分
   * @param geom
   * @param geomB
   * @returns
   */
  const symDifference = (geom, geomB) => {
    const jstsGeom = OLParser.read(geom);
    const jstsGeomB = OLParser.read(geomB);

    if (!jstsGeom.isValid() || !jstsGeomB.isValid()) {
      console.error('几何对象出现拓扑错误,请检查修复');

      return null;
    }

    const difference = jstsGeom.symDifference(jstsGeomB);

    return OLParser.write(difference);
  };

  const handlerTool = (type) => {
    setTypes(type);

    if (types1.current === type) {
      isDraw.current = !isDraw.current;
    } else {
      isDraw.current = true;
    }

    types1.current = type;
  };

  useEffect(() => {
    if (!mapRef.current) {
      init();
      window.addEventListener('keydown', (e) => {
        if (e.ctrlKey && e.keyCode === 90) {
          console.log(e, 'ctrl Z');
        }

        if (e.ctrlKey) {
          console.log(e, 'ctrl');
        }
      });
    }
  }, []);

  return (
    <div className={styless.opRoot}>
      <div className="tool">
        <button type="button" onClick={() => handlerTool(1)}>
          融合
        </button>
        <button type="button" onClick={() => handlerTool(2)}>
          删除
        </button>
      </div>
      <div id="mapContainer" style={{ height: '100%' }}></div>
    </div>
  );
};

export default ModelSampling;

效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值