MapInfo Pro

 View Only
Expand all | Collapse all

Voronoi with less than 3 points

  • 1.  Voronoi with less than 3 points

    Posted 12-07-2022 05:38
    Edited by Ryan Cook 12-07-2022 08:52
    Hello.

    I am confused as to why a region cannot be divided into voronois using less than 3 points. A single point should return a copy of the region, while two points should comfortably divide the region into two. When running the voronoi function as part of a loop within a program that works through a region table, that MapInfo errors out when a region contains 1 or 2 points is rather frustrating. Is there a workaround for this?

    Ryan

    ------------------------------
    Ryan Cook
    Knowledge Community Shared Account
    ------------------------------


  • 2.  RE: Voronoi with less than 3 points

    Employee
    Posted 12-08-2022 04:50
      |   view attached
    It seems we are doing the same work simultaneously, Ryan.

    I have just been through this and created the process below. I have also attached my code.
    It does require that you have the tables in a map window.

    It will loop through your polygon table and for each polygon select the points that lie inside.
    Depending on the number of points, it will do one of four things:
    0. Ignore the polygon
    1. Copy the polygon as it is
    2. Split the polygon with a line connecting the two points that has been rotated 90 degrees at the centroid.
    3. Create Voronoi polygons inside the polygon

    Option two became a bit cumbersome as I couldn't find any spatial operations that could do this on a spatial object variable so I had to do some table magic instead and use targets.

    You'll have to modify the column part to match your columns. Maybe that part can be improved a bit.

    Let me know what you think and if you have suggestions on how to improve it
    Include "MapBasic.def"
    
    Dim	nRowIDPolygon, nRowIDVoronoi As Integer,
    	oPolygon, oPoint1, oPoint2, oLine, oCutter As Object,
    	sQuery1, sQuery2 As String,
    	aObj As Alias
    
    Fetch First From PolygonTable
    Do Until EOT(PolygonTable)
    	nRowIDPolygon	= PolygonTable.ROWID
    	oPolygon		= PolygonTable.OBJ
    
    	Set Map Window FrontWindow() Layer PolygonTable Editable On
    
    	Select * From PolygonTable
    		Where ROWID = nRowIDPolygon
    	sQuery1	= SelectionInfo(SEL_INFO_SELNAME)
    
    	Set Target On
    
    	Select * From shops
    		Where OBJ Within oPolygon
    	sQuery2	= SelectionInfo(SEL_INFO_SELNAME)
    
    	Do Case SelectionInfo(SEL_INFO_NROWS)
    		Case 0
    			'**No points selected within boundary
    			Print "Whoops, some territories with no points - need to handle that manually -> ignore these"
    
    		Case 1
    			Print "Whoops, some territories with one point - need to handle that manually"
    			Insert Into VoronoiTable
    				(Address, City, State, Zip, OBJ)
    				Select Address, City, State, Zip, OBJ From sQuery2
    			Update VoronoiTable
    				Set OBJ = oPolygon
    				Where ROWID = TableInfo(VoronoiTable, TAB_INFO_NROWS)
    			Close Table sQuery2
    
    		Case 2
    			Print "Whoops, some territories with two points - need to handle that manually"
    			Set Map Window FrontWindow() Layer VoronoiTable Editable On
    
    			aObj = sQuery2 & ".obj"
    			Fetch First From sQuery2
    			oPoint1 = aObj
    			Fetch Next From sQuery2
    			oPoint2 = aObj
    			oLine = ConnectObjects(oPoint1, oPoint2, TRUE)
    			oLine = RotateAtPoint(oLine, 90, Centroid(oLine))
    
    			Insert Into VoronoiTable
    				(obj)
    				Values (oPolygon)
    			nRowIDVoronoi	= TableInfo(VoronoiTable, TAB_INFO_NROWS)
    			Select * From VoronoiTable
    				Where ROWID = nRowIDVoronoi
    			Set Target On
    			Insert Into VoronoiTable
    				(obj)
    				Values (oLine)
    			nRowIDVoronoi	= TableInfo(VoronoiTable, TAB_INFO_NROWS)
    			Select * From VoronoiTable
    				Where ROWID = nRowIDVoronoi
    			Create Cutter Into Target
    
    			Fetch Last From VoronoiTable
    			oCutter = VoronoiTable.OBJ
    
    			Delete From VoronoiTable
    				Where ROWID = (nRowIDVoronoi - 1)
    			Delete From VoronoiTable
    				Where ROWID = (nRowIDVoronoi + 0)
    			Delete From VoronoiTable
    				Where ROWID = (nRowIDVoronoi + 1)
    
    			Insert Into VoronoiTable
    				(Address, City, State, Zip, OBJ)
    				Select Address, City, State, Zip, OBJ From sQuery2
    			Update VoronoiTable
    				Set OBJ = Erase(oPolygon, oCutter)
    				Where ROWID = TableInfo(VoronoiTable, TAB_INFO_NROWS) - 1
    			Update VoronoiTable
    				Set OBJ = Overlap(oPolygon, oCutter)
    				Where ROWID = TableInfo(VoronoiTable, TAB_INFO_NROWS)
    
    			Set Map Window FrontWindow() Layer PolygonTable Editable On
    			Close Table sQuery2
    
    
    		Case Else
    			Create Object As Voronoi
    				from Selection
    				Into Table VoronoiTable
    				Data Address = Address, City = City, State = State, Zip = Zip
    			Close Table sQuery2
    
    	End Case
    
    	Close Table sQuery1
    	Fetch Next From PolygonTable
    
    Loop​


    ------------------------------
    Peter Horsbøll Møller
    Principal Presales Consultant | Distinguished Engineer
    Precisely | Trust in Data
    ------------------------------

    Attachment(s)



  • 3.  RE: Voronoi with less than 3 points

    Employee
    Posted 12-08-2022 04:52
    PS: I think the limitation comes from the fact that if you don't have a target object, MapInfo Pro will use the extent of the points as the extent of the Voronoi polygons created. A single point has no extent, and two points only have a 1-dimensional "extent".

    ------------------------------
    Peter Horsbøll Møller
    Principal Presales Consultant | Distinguished Engineer
    Precisely | Trust in Data
    ------------------------------



  • 4.  RE: Voronoi with less than 3 points

    Posted 12-08-2022 05:23
    Edited by Peter Møller 12-08-2022 05:47
    Hi Peter, thanks for your answer and code - I will take a look in more detail shortly. Currently my code simply uses an IF statement condition of SelectionInfo(SEL_INFO_NROWS) >=3 to either proceed with the voronoi function or skip it for that region (and thus avoiding MapInfo breaking). I'm marking each region that does pass the NROWS condition with "done" means I can produce a table at the end with the regions that failed for easy identification. 

    For our purposes, 0 and 1 points can yield the same result: a copy of the entire region object into the voronoi table. So above, case 0 and 1 could be merged. 

    I like your workaround for case 2; we had a similar idea but totally missed that a cutting object does not need to meet the extent of the target object - that's a useful thing to keep in mind!

    To your second post, I'm afraid I'm a little confused: it would appear both our codes HAVE been setting a target object (the region). Here is a snippet of my main loop (the full code is doing a lot of other stuff like buffering certain points on Type instead of voronoi-ing them, which isn't really relevant here - though I did consider buffering single/double points as a workaround for the Voronoi problem, where we primarily want to end up with a unique region per point in our point table). Apologies, it won't be as clean as yours!:

    ===

    Fetch First From Regions_Plus_OTB

    Do Until EOT(Regions_Plus_OTB)
      Reggy = Regions_Plus_OTB.nReg

      Select *
      From Regions_Plus_OTB
      Where nReg = Reggy
      into TargetRegion
      Set Target

      InputNodeVor = sInputNode + ".vor"

      Select *
        From sinputNode
        Where InputNodeVor = "" and obj Within (Select obj from Regions_Plus_OTB where nReg = Reggy)
        Into RyanRegion
      If SelectionInfo(SEL_INFO_NROWS) >=3
      Then

        Update RyanRegion
          Set Vor = "Done"

        Create Object As Voronoi from RyanRegion Into Table Multi_Region_voronois Data nNode=nNode

      Else
        Select *
          From Regions_Plus_OTB
          Where nReg = Reggy
          into TargetRegion

        print TargetRegion.col1
      End If

      Fetch Next From Regions_Plus_OTB
    Loop

    ===

    Note, you might notice another workaround in there, which helps avoid the instance of points receiving multiple voronois in their name (which occurs when a point sits on the border of two or more regions). Nothing special, just a field update against points that have been selected and voronoi-ed before, so they are discluded from future selections and thus the output is just one region per point. 

    But yeah, isn't Set Target relevant here?

    Thanks again for your help, with this and previous issues!


    ------------------------------
    Ryan Cook
    Knowledge Community Shared Account
    ------------------------------



  • 5.  RE: Voronoi with less than 3 points

    Employee
    Posted 12-08-2022 05:46
    Hi

    Your code is quite elegant too, Ryan.

    In fact, you have some nice improvements to my version such as always selecting into a name query. I know that's best practice but for some reason, I ignored that here. I'll change that in my code too.

    I also like your solution to avoid points being used in multiple polygons when they sit right on the border. I hadn't considered that issue.

    I have two suggestions on how you may improve your code:

    1. Sub-Select
    Change the sub-select from this:
    and obj Within (Select obj from Regions_Plus_OTB where nReg = Reggy)
    to this:
    and obj Within (Select obj from TargetRegion)

    2. Print for less than 3 points
    You already have a cursor pointing to that record so you don't need to do a select.
    Change this:
    Select *
      From Regions_Plus_OTB
      Where nReg = Reggy
      into TargetRegion
    print TargetRegion.col1
    To this:
    Print Regions_Plus_OTB.col1

    These are just suggestions. Just ignore these if you prefer, and maybe you do this for a reason I can't see.

    Thanks for sharing your code. We can all learn from each other.

    ------------------------------
    Peter Horsbøll Møller
    Principal Presales Consultant | Distinguished Engineer
    Precisely | Trust in Data
    ------------------------------



  • 6.  RE: Voronoi with less than 3 points

    Posted 12-08-2022 06:47
    Thanks Peter, your revisions are sensible and cut out some of the lumbering around I was doing to achieve the same thing! I will look to incorporate your cutting idea into my code and let you know how it goes. 

    I think you misunderstand me about the Set Target point. I know that we need to Set Target to instruct MapInfo to crop the voronois to a given region - hence my inclusion of the instruction in my code. But to your point: "I think the limitation comes from the fact that if you don't have a target object, MapInfo Pro will use the extent of the points as the extent of the Voronoi polygons created. A single point has no extent, and two points only have a 1-dimensional "extent"." - we ARE setting a target object (the region), so we DO have an extent, yet MapInfo still refuses to voronoi less than 3 points. As I said, it doesn't really make any sense to me why (a voronoi diagram from two points is perfectly logical).




    ------------------------------
    Ryan Cook
    Knowledge Community Shared Account
    ------------------------------



  • 7.  RE: Voronoi with less than 3 points

    Employee
    Posted 12-08-2022 08:00
    Ah, okay, maybe I wasn't too clear.

    I think the code works the same with and without a target. And that's why it does require at least three points.

    I agree with you that it should work with 1 and 2 points too if you have a target. One for the Precisely Ideas.

    ------------------------------
    Peter Horsbøll Møller
    Principal Presales Consultant | Distinguished Engineer
    Precisely | Trust in Data
    ------------------------------



  • 8.  RE: Voronoi with less than 3 points

    Posted 12-08-2022 09:14
    Edited by Ryan Cook 12-08-2022 09:27
    Gotcha - MapInfo is running the same code for both instances and there is a protection there for when there are no extents other than what MapInfo creates itself using a convex hull. It's sunk in now! Seems like an easy fix for MapInfo to make for future versions. 



    ------------------------------
    Ryan Cook
    Knowledge Community Shared Account
    ------------------------------



  • 9.  RE: Voronoi with less than 3 points

    Employee
    Posted 12-08-2022 05:51
    PS:

    Set Target is needed for creating Voronoi. At least if you want to extent/limit these Voronoi polygons to an existing polygon.

    I had hoped I could create the Cutter object without using Set Target. Set Target uses a map and a table. I had hoped to just do some spatial operations on a spatial variable to avoid having to clean up my table. Notice the Delete statements in my example.

    ------------------------------
    Peter Horsbøll Møller
    Principal Presales Consultant | Distinguished Engineer
    Precisely | Trust in Data
    ------------------------------



  • 10.  RE: Voronoi with less than 3 points

    Posted 12-08-2022 09:39
    So I have been merging your code into mine and I have got it to work...ish. Below is the result when I run my program on some simple test tables: Regions and Nodes:

    Regions in RED. 
    Region 1 (left) has 7 points within it, thus passes the 'voronoi test' and is processed normally. 
    Region 2 (right) has a single point in it and triggers the special instance of inserting the entire target region into the voronoi table.
    Region 3 (middle-bottom) has two points within it. The code runs without breaking and its clear the cutter is doing it's job BUT for some reason I'm ending up with only one of the split regions in my final table (hence the white space).

    I'm not sure why, any thoughts?


    ------------------------------
    Ryan Cook
    Knowledge Community Shared Account
    ------------------------------



  • 11.  RE: Voronoi with less than 3 points

    Employee
    Posted 12-08-2022 10:10
    Can you share the code for option two?

    A few things that can go wrong:
    - You only insert one of the records
    - You delete too many records after creating the cutter object
    - For an unknown reason you get one of the polygons deleted

    ------------------------------
    Peter Horsbøll Møller
    Principal Presales Consultant | Distinguished Engineer
    Precisely | Trust in Data
    ------------------------------



  • 12.  RE: Voronoi with less than 3 points

    Posted 12-08-2022 10:13
    Edited by Ryan Cook 12-08-2022 10:15
    One sec...

    ------------------------------
    Ryan Cook
    Knowledge Community Shared Account
    ------------------------------



  • 13.  RE: Voronoi with less than 3 points

    Posted 12-08-2022 10:15
    Edited by Peter Møller 12-09-2022 01:32
    If SelectionInfo(SEL_INFO_NROWS) = 2 Then

      Dim nRowIDPolygon, nRowIDVoronoi As Integer,
          oPolygon, oPoint1, oPoint2, oLine, oCutter As Object,
          aObj As Alias

      nRowIDPolygon = inputregionROWID
      oPolygon = TargetRegion.obj
      aObj = RyanRegion & ".obj"

      Fetch First from RyanRegion
      oPoint1 = aObj
      Fetch Next From RyanRegion
      oPoint2 = aObj
      oLine = ConnectObjects(oPoint1, oPoint2, TRUE)
      oLine = RotateAtPoint(oLine, 90, Centroid(oLine))

      Insert Into Multi_Region_Voronois
        (obj)
        Values (oPolygon)
      nRowIDVoronoi = TableInfo(Multi_Region_Voronois, TAB_INFO_NROWS)
      Select * From Multi_Region_Voronois
        Where ROWID = nRowIDVoronoi
      Set Target On

      Insert Into Multi_Region_Voronois
        (obj)
        Values (oLine)
      nRowIDVoronoi = TableInfo(Multi_Region_Voronois, TAB_INFO_NROWS)
      Select * From Multi_Region_Voronois
        Where ROWID = nRowIDVoronoi
      Create Cutter Into Target

      Fetch Last From Multi_Region_Voronois
      oCutter = Multi_Region_Voronois.OBJ

      Delete From Multi_Region_Voronois
        Where ROWID = (nRowIDVoronoi - 1)
      Delete From Multi_Region_Voronois
        Where ROWID = (nRowIDVoronoi + 0)
      Delete From Multi_Region_Voronois
        Where ROWID = (nRowIDVoronoi + 1)

      Insert Into Multi_Region_Voronois (OBJ)
        Select OBJ From TargetRegion
      Update Multi_Region_Voronois
        Set OBJ = Erase(oPolygon, oCutter)
        Where ROWID = TableInfo(Multi_Region_Voronois, TAB_INFO_NROWS) - 1
      Update Multi_Region_Voronois
        Set OBJ = Overlap(oPolygon, oCutter)
        Where ROWID = TableInfo(Multi_Region_Voronois, TAB_INFO_NROWS)
    Else End If

    ------------------------------
    Ryan Cook
    Knowledge Community Shared Account
    ------------------------------



  • 14.  RE: Voronoi with less than 3 points

    Posted 12-08-2022 10:25
    Incidentally I just removed the third delete statement Where ROWID = (nRowIDVoronoi + 1) and it works!

    ------------------------------
    Ryan Cook
    Knowledge Community Shared Account
    ------------------------------



  • 15.  RE: Voronoi with less than 3 points

    Employee
    Posted 12-09-2022 01:48
    Still early morning here so the only thing I wonder is if your Multi_Region_Voronois layer is editable.
    I start my process when having selected two points by making the Voronoi table editable.
    Set Map Window FrontWindow() Layer VoronoiTable Editable On​
    But I don't that's the problem either... If it was, you wouldn't be able to get the cutter object from the table which you are able to do.

    Another thing that might be the cause is this statement:
    Insert Into Multi_Region_Voronois (OBJ)
      Select OBJ From TargetRegion​

    You should be inserting the two records from your point table but I think you are inserting the current polygon record.
    That might explain why you are a record short

    ------------------------------
    Peter Horsbøll Møller
    Principal Presales Consultant | Distinguished Engineer
    Precisely | Trust in Data
    ------------------------------