Skip to content

Commit

Permalink
Merge pull request #17 from rowanwins/better-handle-endpoint
Browse files Browse the repository at this point in the history
Add more robust intersection checking, inc for end of segments
  • Loading branch information
rowanwins authored Nov 20, 2024
2 parents 3a111b4 + 6f1d3c8 commit c1be69d
Show file tree
Hide file tree
Showing 25 changed files with 269 additions and 150 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.0.0
- More robust detection of intersections (inc on endpoints)
- Remove webpack and ava as dev dependencies, and move to vite & vitest

## 1.5.0
- Add buble to rollup build

Expand Down
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,7 @@ Tested against
// - Fastest is sweepline (this library)
// Chile - Vertical geometry (17,000 vertices)
// bentleyOttmann x 50.22 ops/sec ±1.75% (65 runs sampled)
// sweepline x 35.64 ops/sec ±1.20% (62 runs sampled)
// - Fastest is bentleyOttmann (although it doesn't find intersection)
````

## Contributing
Expand Down
Binary file removed debug/2273e3d8ad9264b7daa5bdbf8e6b47f8.png
Binary file not shown.
Binary file removed debug/401d815dc206b8dc1b17cd0e37695975.png
Binary file not shown.
Binary file removed debug/44a526eed258222515aa21eaffd14a96.png
Binary file not shown.
Binary file removed debug/4f0283c6ce28e888000e978e537a6a56.png
Binary file not shown.
7 changes: 7 additions & 0 deletions debug/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Vue 3 + Vite

This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.

## Recommended IDE Setup

- [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar)
Binary file removed debug/a6137456ed160d7606981aa57c559898.png
Binary file not shown.
1 change: 0 additions & 1 deletion debug/build.js

This file was deleted.

34 changes: 34 additions & 0 deletions debug/gj.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {createFilter, dataToEsm} from '@rollup/pluginutils'

export default function json (options = {
include: ['**/*.json', '**/*.geojson']
}) {
const filter = createFilter(options.include, options.exclude)
const indent = 'indent' in options ? options.indent : '\t'

return {
name: 'geojson',

// eslint-disable-next-line no-shadow
transform (json, id) {
if (!filter(id)) return null
try {
const parsed = JSON.parse(json)
return {
code: dataToEsm(parsed, {
preferConst: options.preferConst,
compact: options.compact,
namedExports: options.namedExports,
indent
}),
map: {mappings: ''}
}
} catch (err) {
const message = 'Could not parse GeoJSON file'
const position = parseInt(/[\d]/.exec(err.message)[0], 10)
this.warn({message, id, position})
return null
}
}
}
}
8 changes: 5 additions & 3 deletions debug/index.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>sweepline debugger</title>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script src="build.js"></script>
<script type="module" src="/src/main.js"></script>
</body>
</html>
Binary file added debug/public/favicon.ico
Binary file not shown.
7 changes: 3 additions & 4 deletions debug/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,12 @@ L.Icon.Default.mergeOptions({
iconUrl: markerIcon,
shadowUrl: markerShadow
})
const trouble = require('../../test/fixtures/simple/MultiLinestring.geojson')
import data from '../../test/fixtures/notSimple/chileKinked.geojson'
export default {
name: 'App',
mounted () {
const layer = L.geoJSON(trouble)
const layer = L.geoJSON(data)
let map = window.map = L.map('app', {
crs: L.CRS.Simple
Expand All @@ -35,7 +34,7 @@ export default {
map.addControl(new L.Coordinates())
const intersections = sweepline(trouble)
const intersections = sweepline(data)
const layerGroup = L.layerGroup([]).addTo(map)
intersections.forEach(function (ip) {
Expand Down
7 changes: 2 additions & 5 deletions debug/src/main.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import Vue from 'vue'
import { createApp } from 'vue'
import App from './App.vue'

new Vue({
el: '#app',
render: h => h(App)
})
createApp(App).mount('#app')
11 changes: 11 additions & 0 deletions debug/vite.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import gj from './gj'

// https://vitejs.dev/config/
export default defineConfig({
base: '/sweepline-intersections/debug/dist/',
plugins: [
vue(), gj()
]
})
62 changes: 0 additions & 62 deletions debug/webpack.config.js

This file was deleted.

47 changes: 17 additions & 30 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "sweepline-intersections",
"version": "1.5.0",
"version": "2.0.0",
"type": "module",
"description": "A module to check if a polygon self-intersects using a sweepline algorithm",
"main": "dist/sweeplineIntersections.js",
"module": "dist/sweeplineIntersections.esm.js",
Expand All @@ -10,58 +11,44 @@
"scripts": {
"bench": "npm run build && node test/benchmark.js",
"build": "rollup -c",
"build:debug": "cross-env webpack --config debug/webpack.config.js --mode production",
"debug": "cross-env webpack-dev-server --config debug/webpack.config.js --mode development --open --hot",
"test": "ava --verbose"
"debug": "vite serve debug",
"test": "vitest test/cases.test.js"
},
"files": [
"dist/",
"types.d.ts"
],
"ava": {
"files": [
"test/test.e2e.js",
"test/*.spec.js"
],
"require": [
"esm"
]
},
"author": "",
"license": "MIT",
"dependencies": {
"tinyqueue": "^2.0.0"
"@rollup/plugin-commonjs": "^28.0.1",
"ava": "^6.2.0",
"tinyqueue": "^2.0.0",
"vitest": "^2.1.5"
},
"devDependencies": {
"@rollup/plugin-buble": "^0.21.3",
"@rollup/plugin-commonjs": "^11.1.0",
"@rollup/plugin-node-resolve": "^7.1.3",
"@rollup/plugin-strip": "^1.3.2",
"@rollup/plugin-buble": "^1.0.3",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-strip": "^3.0.4",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/pluginutils": "^5.1.3",
"@types/geojson": "7946.0.8",
"@vitejs/plugin-vue": "^5.2.0",
"2d-polygon-self-intersections": "^1.3.1",
"ava": "^1.0.1",
"benchmark": "^2.1.4",
"bentley-ottmann-intersections": "0.0.1",
"cross-env": "^5.2.0",
"css-loader": "^2.1.0",
"eslint": "^5.12.0",
"eslint-config-mourner": "^3.0.0",
"esm": "^3.0.84",
"file-loader": "^3.0.1",
"geojson-polygon-self-intersections": "^1.2.0",
"glob": "^7.1.3",
"json-loader": "^0.5.7",
"leaflet": "^1.4.0",
"load-json-file": "^5.3.0",
"nyc": "^13.1.0",
"rollup": "^2.7.6",
"rollup-plugin-terser": "^4.0.2",
"vue": "^2.5.22",
"vue-loader": "^15.6.2",
"vue-template-compiler": "^2.5.22",
"webpack": "^4.29.0",
"webpack-cli": "^3.2.1",
"webpack-dev-server": "^3.1.14"
"rollup": "^4.0.0",
"vite": "^5.4.11",
"vue": "^3.0.0"
},
"repository": {
"type": "git",
Expand Down
4 changes: 2 additions & 2 deletions rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import commonjs from '@rollup/plugin-commonjs'
import resolve from '@rollup/plugin-node-resolve'
import strip from '@rollup/plugin-strip'
import {terser} from 'rollup-plugin-terser'
import buble from '@rollup/plugin-buble';
import terser from '@rollup/plugin-terser'
import buble from '@rollup/plugin-buble'

const output = (input, file, format, plugins) => ({
input,
Expand Down
4 changes: 4 additions & 0 deletions src/Event.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,9 @@ export default class Event {
isSamePoint (eventToCheck) {
return this.p.x === eventToCheck.p.x && this.p.y === eventToCheck.p.y
}

asNewXY () {
return [this.p.x, this.p.y]
}
}

8 changes: 7 additions & 1 deletion src/compareEvents.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ export function checkWhichEventIsLeft (e1, e2) {
if (e1.p.x > e2.p.x) return 1
if (e1.p.x < e2.p.x) return -1

if (e1.p.x === e2.p.x && (e1.featureId !== e2.featureId || e1.ringId !== e2.ringId)) {
if (e1.isLeftEndpoint && !e2.isLeftEndpoint) return -1
}

if (e1.p.y !== e2.p.y) return e1.p.y > e2.p.y ? 1 : -1
return 1
}
Expand All @@ -10,6 +14,8 @@ export function checkWhichSegmentHasRightEndpointFirst (seg1, seg2) {
if (seg1.rightSweepEvent.p.x > seg2.rightSweepEvent.p.x) return 1
if (seg1.rightSweepEvent.p.x < seg2.rightSweepEvent.p.x) return -1

if (seg1.rightSweepEvent.p.y !== seg2.rightSweepEvent.p.y) return seg1.rightSweepEvent.p.y < seg2.rightSweepEvent.p.y ? 1 : -1
if (seg1.rightSweepEvent.p.y !== seg2.rightSweepEvent.p.y) {
return seg1.rightSweepEvent.p.y < seg2.rightSweepEvent.p.y ? 1 : -1
}
return 1
}
30 changes: 23 additions & 7 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import {orient2d} from 'robust-predicates'

export function testSegmentIntersect (seg1, seg2) {
if (seg1 === null || seg2 === null) return false

if (seg1.leftSweepEvent.ringId === seg2.leftSweepEvent.ringId &&
(seg1.rightSweepEvent.isSamePoint(seg2.leftSweepEvent) ||
seg1.rightSweepEvent.isSamePoint(seg2.leftSweepEvent) ||
seg1.rightSweepEvent.isSamePoint(seg2.rightSweepEvent) ||
seg1.leftSweepEvent.isSamePoint(seg2.leftSweepEvent) ||
seg1.leftSweepEvent.isSamePoint(seg2.rightSweepEvent))) return false

const x1 = seg1.leftSweepEvent.p.x
const y1 = seg1.leftSweepEvent.p.y
const x2 = seg1.rightSweepEvent.p.x
Expand All @@ -17,6 +12,27 @@ export function testSegmentIntersect (seg1, seg2) {
const x4 = seg2.rightSweepEvent.p.x
const y4 = seg2.rightSweepEvent.p.y

const score1 = orient2d(x1, y1, x2, y2, x3, y3)
const score2 = orient2d(x1, y1, x2, y2, x4, y4)

if (score1 > 0 && score2 > 0) return false
else if (score1 < 0 && score2 < 0) return false

if (seg1.leftSweepEvent.ringId === seg2.leftSweepEvent.ringId) {
if (
seg1.rightSweepEvent.isSamePoint(seg2.leftSweepEvent) ||
seg1.rightSweepEvent.isSamePoint(seg2.rightSweepEvent) ||
seg1.leftSweepEvent.isSamePoint(seg2.leftSweepEvent) ||
seg1.leftSweepEvent.isSamePoint(seg2.rightSweepEvent)
) return false
} else {
if (seg1.rightSweepEvent.isSamePoint(seg2.leftSweepEvent)) return seg2.leftSweepEvent.asNewXY()
if (seg1.rightSweepEvent.isSamePoint(seg2.rightSweepEvent)) return seg2.rightSweepEvent.asNewXY()
if (seg1.leftSweepEvent.isSamePoint(seg2.leftSweepEvent)) return seg2.leftSweepEvent.asNewXY()
if (seg1.leftSweepEvent.isSamePoint(seg2.rightSweepEvent)) return seg2.rightSweepEvent.asNewXY()
}


const denom = ((y4 - y3) * (x2 - x1)) - ((x4 - x3) * (y2 - y1))
const numeA = ((x4 - x3) * (y1 - y3)) - ((y4 - y3) * (x1 - x3))
const numeB = ((x2 - x1) * (y1 - y3)) - ((y2 - y1) * (x1 - x3))
Expand Down
Loading

0 comments on commit c1be69d

Please sign in to comment.