Is it possible to intersect/clip line with polygon using .NetTopologySuite? - c#

I have question regarding intersecting line segment with polygon. I have some line (green) that represents some walking path and some restriction polygon (black) that represents polygon.
You can see in image below:
I'm now wondering if it is possible to extract line segments that are outside polygon (red lines in upper left corner)
First, I created polygon using something like this:
var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326);
var vertices = new List<Coordinate>();
foreach (var coords in stringPolygon)
{
var coordinates = coords.Split(",");
var x = double.Parse(coordinates[0]);
var y = double.Parse(coordinates[1]);
var newCoordinate = new Coordinate(y,x);
vertices.Add(newCoordinate);
}
var outerRing = geometryFactory.CreateLinearRing(vertices.ToArray());
spatialData.FieldPolygon = geometryFactory.CreatePolygon(outerRing);
Then created linestring like this:
var vertices = new List<Coordinate>();
foreach (var trip in tripSegments.Data)
{
var newCoordinate = new Coordinate(trip.Lng, trip.Lat);
vertices.Add(newCoordinate);
}
spatialData.TripLine = geometryFactory.CreateLineString(vertices.ToArray());
Tried with Intersection
var intersect = spatialData.FieldPolygon.Boundary.Intersection(spatialData.TripLine);
And also with difference but without an luck
var intersect = spatialData.FieldPolygon.Boundary.Difference(spatialData.TripLine);
Also tried with WKT Reader and combination of intersection and difference like this (I'm wondering if this is even right approach):
var reader = new WKTReader();
var targetMultiPolygon = reader.Read(spatialData.TripLine.ToString());
var bufferPolygon = reader.Read(spatialData.FieldPolygon.Boundary.ToString());
var intersection = targetMultiPolygon.Intersection(bufferPolygon);
var targetClipped = targetMultiPolygon.Difference(intersection);
var wktTargetAfterClip = targetClipped.ToString();
But I got error like this (when using WKT approach).
TopologyException: found non-noded intersection between LINESTRING(26.563827556466403 43.52431484490672, 26.56386783617048 43.52429417990681) and LINESTRING(26.565492785081837 43.52349421574761, 26.56386783617048 43.52429417990681) [ (26.56386783617048, 43.52429417990681, NaN) ]
UPDATE
I've fixed the topology issue whit WKT Reader with
var bufferPolygon = reader.Read(spatialData.FieldPolygon.Boundary.ToString());
on reader I added Boundary property and that fixed topology issue. But main problem still remains as it can be seen here.
I used this block of code to extract lines that don't intersect and that are added to different layer but as you can see in red square there are some lines that are green and they should be purple (dashed) because they are outside black polygon but they are still green.
var outsideLines = new List<ILineString>();
foreach (ILineString lineString in geometries.Geometries)
{
var isIntersecting = lineString.Intersects(spatialData.FieldPolygon.Boundary);
if (!isIntersecting)
{
outsideLines.Add(lineString);
}
}
spatialData.IntersectedMultiLine = geometryFactory.CreateMultiLineString(outsideLines.ToArray());
Now, my main questions is: is it possible to extract lines that are outside polygon and am I on the right track?

Related

Latitude and Longitude to pixel-value in GDAL

I'm trying to read a GeoTIFF with one band which stores a value between 0 and 3 ( 255 is no maks ). My Goal is to write a little program which takes in a latitude/longitude and returns the fitting pixel value at the geocoordinate in the geotiff.
I downloaded it from here : https://dataverse.harvard.edu/dataset.xhtml?persistentId=doi:10.7910/DVN/QQHCIK
And if you wanna take a look at it... heres the download link. https://drive.google.com/file/d/104De_YQN1V8tSbj2uhIPO0FfowjnInnX/view?usp=sharing
However, my code does not work. I cant tell you what is wrong, it just outputs the wrong pixel value every damn time. I guess either my geocordinate to pixel transformation is wrong or theres a huge logic mistake.
This was my first try, it prints the wrong value for the geo-coordinate.
Furthermore it crashes with some geocoordinates, a negative longitude makes it crashes because this will make pixelY negative which causes an exception in the raster method.
GdalConfiguration.ConfigureGdal();
var tif = "C:\\Users\\Lars\\Downloads\\pnv_biome.type_biome00k_c_1km_s0..0cm_2000..2017_v0.1.tif";
var lat = 51.0;
var lon = 8.3;
using (var image = Gdal.Open(tif, Access.GA_ReadOnly)) {
var bOneBand = image.GetRasterBand(1);
var width = bOneBand.XSize;
var height = bOneBand.YSize;
// Geocoordinate ( lat, lon ) to pixel in the tiff ?
var geoTransform = new double[6];
image.GetGeoTransform(geoTransform);
Gdal.InvGeoTransform(geoTransform, geoTransform);
var bOne = new int[1];
Gdal.ApplyGeoTransform(geoTransform, lat, lon, out var pixelXF, out var pixelYF);
var pixelX = (int)Math.Floor(pixelXF);
var pixelY = (int)Math.Floor(pixelYF);
// Read pixel
bOneBand.ReadRaster(pixelX, pixelY, 1, 1, bOne, 1, 1,0,0);
Console.WriteLine(bOne[0]); // bOne[0] contains wrong data
}
My second attempt looks like the following... but also outputs the wrong pixel value for the given coordinates. It also crashes with some geocoordinates.
GdalConfiguration.ConfigureGdal();
var tif = "C:\\Users\\Lars\\Downloads\\pnv_biome.type_biome00k_c_1km_s0..0cm_2000..2017_v0.1.tif";
var lat = 24.7377; // 131.5847
var lon = 131.5847;
using (var image = Gdal.Open(tif, Access.GA_ReadOnly)) {
var bOneBand = image.GetRasterBand(1);
var bOne = new int[1];
// Spatial reference to transform latlng to map coordinates... from python code
var point_srs = new SpatialReference("");
var file_srs = new SpatialReference(image.GetProjection());
point_srs.ImportFromEPSG(4326);
point_srs.SetAxisMappingStrategy(AxisMappingStrategy.OAMS_TRADITIONAL_GIS_ORDER);
var mapCoordinates = new double[2]{ lat, lon};
var transform = new CoordinateTransformation(point_srs, file_srs);
transform.TransformPoint(mapCoordinates);
// Map coordinates to pixel coordinates ?
var geoTransform = new double[6];
image.GetGeoTransform(geoTransform);
Gdal.InvGeoTransform(geoTransform, geoTransform);
Gdal.ApplyGeoTransform(geoTransform, mapCoordinates[0], mapCoordinates[1], out var pixelXF, out var pixelYF);
var pixelX = (int)pixelXF;
var pixelY = (int)pixelYF;
bOneBand.ReadRaster(pixelX, pixelY, 1, 1, bOne, 1, 1, 0,0);
Console.WriteLine(bOne[0]); // bOne[0] contains wrong value
}
What is wrong with my code, why does it output the wrong pixel value ?
Any help appreciated !
Perhaps you are using lat/lon where it should be lon/lat? It would be helpful if you printed some values such as mapCoordinates.
Here is how you can do this with R, for comparison:
library(terra)
r = rast("pnv_biome.type_biome00k_c_1km_s0..0cm_2000..2017_v0.1.tif")
xy = cbind(c(8.3, 131.5847), c(51.0, 24.7377))
extract(r, xy)
# pnv_biome.type_biome00k_c_1km_s0..0cm_2000..2017_v0.1
#1 9
#2 NA
And the zero-based row/col numbers
rowColFromCell(r, cellFromXY(r, xy)) - 1
# [,1] [,2]
#[1,] 4364 22596
#[2,] 7515 37390
And you can use describe (a.k.a. GDALinfo) to get some metadata. E.g.
d = describe("pnv_biome.type_biome00k_c_1km_s0..0cm_2000..2017_v0.1.tif")
d[50]
# [1] "Band 1 Block=43200x1 Type=Byte, ColorInterp=Gray"

Dynamically load Polygon with Positions

I'm trying to create a geofence with an uncertain amount of coordinates, but C# and Xamarin Forms Maps won't accept dynamically loaded-in content. I have made sure that there always will be three or more positions to create the geofence. I have tried this, with coordinates being a string array:
Polygon geofence = new Polygon
{
StrokeColor = Color.Green,
FillColor = Color.Green,
Geopath =
{
foreach (string coordinate in coordinates)
{
string[] LongAndLat = coordinate.Split(',');
new Position(Convert.ToDouble(LongAndLat[0]), Convert.ToDouble(LongAndLat[1]));
}
}
};
Which basically tells me that C# doesn't expect a function within the Geopath parameters, but I don't know how to get to where I want without doing it.
Is there a way to do this correctly?
The C# syntax isn't valid. Since the Geopath property is readonly, you have to assign the property later. This solution works:
string[] coordinates = geodata.Split(';');
Polygon geofence = new Polygon
{
StrokeColor = Color.Green,
FillColor = Color.FromRgba(0, 255, 0, 0.4)
};
foreach(string coordinate in coordinates)
{
string[] LongAndLat = coordinate.Split(',');
geofence.Geopath.Add(new Position(Convert.ToDouble(LongAndLat[0]), Convert.ToDouble(LongAndLat[1])));
}

ArrayList conversion using Linq

***I sincerely apologise as I should have posted the complete code.
I have an application where I have an IUIAutomationElementArray and I have cached data regarding bounding rectangles of each IUIAutomationElement from the array. I am then converting these to WPF borders
public class NumberRectangle : Border, IComparable
{
currently I am using iteration to convert the IUIAutomationElementArray to an array list of rectangles seen in the method declaration below.
public ArrayList createRectangles(IUIAutomationElementArray elements)
{
// create an array list to hold the rectangles
rectArray = new ArrayList();
for (int i = 0; i < elements.Length; i++)
{
IUIAutomationElement currentElement = elements.GetElement(i);
//create DragonNumberRectangle to represent automation element
NumberRectangle currentRectangle = new NumberRectangle(currentElement);
//set horizontal and vertical alignment in order to align rectangles properly on window
currentRectangle.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
currentRectangle.VerticalAlignment = System.Windows.VerticalAlignment.Top;
currentRectangle.Height = (currentElement.CachedBoundingRectangle.bottom - currentElement.CachedBoundingRectangle.top);
currentRectangle.Width = (currentElement.CachedBoundingRectangle.right - currentElement.CachedBoundingRectangle.left);
// Thickness object represents Margin property of NumberRectangle (which is basically a Border)
Thickness rectThickness = new Thickness();
//set Left and Top for position of rectangle
rectThickness.Left = (currentElement.CachedBoundingRectangle.left);
rectThickness.Top = (currentElement.CachedBoundingRectangle.top);
currentRectangle.Margin = rectThickness;
// add colour rectangle to the list of rectangles
rectArray.Add(currentRectangle);
}
//sort the rectangles to number from left to right/top to bottom
rectArray.Sort();
return rectArray;
}
I then draw the borders on a WPF window. The issue is that the createRectangles metho takes one second of processing time where the conversion of the element array to rectangles is the predominant time waster.
So the question is can I do this with Linq and how would I do this with Linq, an example would be great as I am not familiar with Linq currently. Perhaps the real question is how do I speed this up?
If you consider LINQ is more elegant than you can use conversion in this way. Surely this is a slower way to convert list of objects.
...
rectArrayList =
from e in elements
select new
{
x = e.x,
y = e.y,
width = e.width,
height = e.height,
};
To achieve a conversion from array to arraylist you could simple do the following:
IUIAutomationElementArray elements = new IUIAutomationElementArray();
//populate that array with data
var arrayList = new ArrayList();
arrayList.Addrange(elements);
Ideally though you should be using a List<T> as opposed to an ArrayList, in which case you could do the following:
List<IUIAutomationElement> myList = new List<IUIAutomationElement>(elements);
Here you are passing the array as a parameter in the constructor of the List.

How to get the center point of a Face or a PlanarFace element in Revit

I'm doing a Revit Macro to get the center point of a part (floor part) to check if it is inside a room or a space.
I couldn't get much of the BoundingBox object which is giving me a point outside the part, so I tried to use the Geometry element internal faces getting the mesh vertices but I'm stuck calculating the mid point.
I'm using a rather naive algorithm shown in the snippet below, but it's giving me false results as it seems to be affected by the initial default of min/max variables.
Any suggestions?
PS: DebugTools is a custom helper class of my own.
public void ZoneDetect()
{
Document doc = this.ActiveUIDocument.Document;
using (Transaction t = new Transaction(doc,"Set Rooms By Region"))
{
t.Start();
FilteredElementCollector fec =
new FilteredElementCollector(doc)
.OfClass(typeof(Part))
.OfCategory(BuiltInCategory.OST_Parts)
.Cast<Part>();
foreach (Part p in fec)
{
Options op = new Options();
op.View=doc.ActiveView;
op.ComputeReferences=true;
GeometryElement gm=p.get_Geometry(op);
Solid so = gm.First() as Solid;
PlanarFace fc=so.Faces.get_Item(0) as PlanarFace;
foreach (PlanarFace f in so.Faces)
{
if (f.Normal == new XYZ(0,0,-1)) fc=f;
}
XYZ max = new XYZ();
XYZ min = new XYZ();
int no = 0;
foreach (XYZ vx in fc.Triangulate().Vertices)
{
// Just for debugging
DebugTools.DrawModelTick(vx,doc,"Max");
doc.Regenerate();
TaskDialog.Show("Point:"+no.ToString(),vx.ToString());
no++;
//Comparing points
if (vx.X>max.X) max=new XYZ (vx.X,max.Y,0);
if (vx.Y>max.Y) max=new XYZ (max.X,vx.Y,0);
if (vx.X<min.X) min=new XYZ (vx.X,min.Y,0);
if (vx.Y<min.Y) min=new XYZ (min.X,vx.Y,0);
}
XYZ mid = new XYZ(max.X-min.X,max.Y-min.Y,0);
DebugTools.DrawModelTick(mid,doc,"Mid");
DebugTools.DrawModelTick(max,doc,"Max");
DebugTools.DrawModelTick(min,doc,"Min");
}
t.Commit();
}
}
It seems like you're looking for the center of gravity of a polygon. An algorithm for that can be found here: Center of gravity of a polygon
Once you have a Face object, you can enumerate its edges to receive a list of vertex points. Use the longest of the EdgeLoops in the face. Collect all the points and make sure that they are in the right order (the start and end points of the edges might need to be swapped).
Daren & Matt thanks a lot for your answers,
Since I'm dealing with rather simple shapes ( mainly rectangles ) I just needed to get a point roughly near the center to test whether it is inside a room, my problem was with the naive algorithm I was using which turned out to be wrong.
I corrected it as follows:
XYZ midSum = Max + Min;
XYZ mid = new XYZ(midSum.X/2 , midSum.Y/2,0);
I will look into refining it using the link you've provided, but as for now I will get into finishing my task in hand.
Many thanks

getting vertex points of GeometryModel3D to draw a wireframe

I've loaded a 3d model using Helix toolking like this
modelGroupScull = importer.Load("C:\\Users\\Robert\\Desktop\\a.obj");
GeometryModel3D modelScull = (GeometryModel3D)modelGroupScull.Children[0];
and I also have _3DTools that can draw lines from point-to-point in 3d space. now to draw a wireframe of my GeometryModel3D I guess I have to cycle to its vertexes and add them to ScreenSpaceLines3D.
ScreenSpaceLines3D wireframe = new ScreenSpaceLines3D();
// need to cycle through all vertexes of modelScull as Points, to add them to wireframe
wireframe.Points.Add(new Point3D(1, 2, 3));
wireframe.Color = Colors.LightBlue;
wireframe.Thickness = 3;
Viewport3D1.Children.Add(wireframe);
But... how do I actually get this vertex points?
EDIT:
Thanks for the answer. It did add the points
ScreenSpaceLines3D wireframe = new ScreenSpaceLines3D();
MeshGeometry3D mg3 = (MeshGeometry3D)modelScull.Geometry;
foreach (Point3D point3D in mg3.Positions)
{
wireframe.Points.Add(point3D);
}
wireframe.Color = Colors.LightBlue;
wireframe.Thickness = 1;
Viewport3D1.Children.Add(wireframe);
but the wireframe is messed up )
(source: gyazo.com)
maybe someone knows of other ways to draw wireframes? )
Normally the trigons are drawn with index buffers (to prevent extra rotations of vertices) Take a look at the TriangleIndices:
if you do something like this: (not tested it)
MeshGeometry3D mg3 = (MeshGeometry3D)modelScull.Geometry;
for(int index=0;index<mg3.TriangleIndices.Count; index+=3)
{
ScreenSpaceLines3D wireframe = new ScreenSpaceLines3D();
wireframe.Points.Add(mg3.Positions[mg3.TriangleIndices[index]]);
wireframe.Points.Add(mg3.Positions[mg3.TriangleIndices[index+1]]);
wireframe.Points.Add(mg3.Positions[mg3.TriangleIndices[index+2]]);
wireframe.Points.Add(mg3.Positions[mg3.TriangleIndices[index]]);
wireframe.Color = Colors.LightBlue;
wireframe.Thickness = 1;
Viewport3D1.Children.Add(wireframe);
}
But, this can create some overdraw (2 lines on the same coordinates) and probably very slow.
If you put each side into a list and use something like a Distinct on it, it will be better.
The problem with the ScreenSpaceLines3D is that will continue the line, instead of create 1 line (start/end).
If you can manage a algoritm that tries to draw you model with 1 line, it will go faster.
Wireframes are very slow in WPF. (because they are created with trigons)
You should find the vertex points in MeshGeometry3D.Positions Property
foreach (var point3D in modelScull.Geometry.Positions)

Categories

Resources