turn on suggestions

Auto-suggest helps you quickly narrow down your search results by suggesting possible matches as you type.

Showing results for

- JMP User Community
- :
- Discussions
- :
- Discussions
- :
- Re: join tables based on location

Topic Options

- Subscribe to RSS Feed
- Mark Topic as New
- Mark Topic as Read
- Float this Topic for Current User
- Bookmark
- Subscribe
- Printer Friendly Page

- Mark as New
- Bookmark
- Subscribe
- Subscribe to RSS Feed
- Get Direct Link
- Email to a Friend
- Report Inappropriate Content

Oct 15, 2015 9:08 AM
(3809 views)

Hi All

Can JMP join (update) tables based on location?

I have a data set of points with coordinates and a shapefile of polygons. I would like to update the dots data table with information from the shapefile based on location. So if a point is in a particular polygon it would update the table with the attributes of the polygon.

Thanks!

Ron

1 ACCEPTED SOLUTION

Accepted Solutions

- Mark as New
- Bookmark
- Subscribe
- Subscribe to RSS Feed
- Get Direct Link
- Email to a Friend
- Report Inappropriate Content

Coincidentally, I just found time to take a quick look. I make no claims for improved efficiency, but only offer it for comparison purposes. I did no checking or testing. For the subset you are using, it runs almost instantly. Note the OS/X paths.

NamesDefaultToHere**(****1****)**;

ClearLog**()**;

// Open dots file - this includes the observation we want to update

dots = Open**(** "$SAMPLE_DATA/Hurricanes.jmp" **)**;

dots << **delete rows****(** dots << **get rows where****(** :Name**(** "Name and ID" **)** != "NOTNAMED_675" **)** **)**;

// Open polygon source

polygons = Open**(** "/Library/Application Support/JMP/12/Maps/US-State-XY.jmp" **)**;

// Open data source - this one includes the information to update

names = Open**(** "/Library/Application Support/JMP/12/Maps/US-State-Name.jmp" **)**;

// Given a 'polygon matrix' pMat, and a shapeID and PartID therein, determines if the point (x, y) lies in the polygon defined by this shape and part

inShapePart =

Function**({**pMat, shapeID, partID, x, y**}**, **{**Default Local**}**,

r = Loc**(**pMat**[****0**,**1****]** == shapeID & pMat**[****0**,**2****]** == partID**)**;

InPolygon**(**x, y, pMat**[**r,**3****]**, pMat**[**r,**4****])**;

**)**;

// Given a 'polygon matrix' pMat, and a shapeID and PartID therein, determines if the point (x, y) lies in the rectangle inscribed by this shape and part

inShapePartRect =

Function**({**pMat, shapeID, partID, x, y**}**, **{**Default Local**}**,

r = Loc**(**pMat**[****0**,**1****]** == shapeID & pMat**[****0**,**2****]** == partID**)**;

maxX = Maximum**(**pMat**[**r, **3****])**;

minX = Minimum**(**pMat**[**r, **3****])**;

maxY = Maximum**(**pMat**[**r, **4****])**;

minY = Minimum**(**pMat**[**r, **4****])**;

if**(** **(**x <= maxX**)** & **(**x >= minX**)** & **(**y <= maxY**)** & **(**y >= minY**)**, **1**, **0****)**;

**)**;

pMat = polygons << **getAsMatrix**; // 'Polygon matrix'

x = Column**(**dots, "Longitude"**)** << **getValues**; // x values

y = Column**(**dots, "Latitude"**)** << **getValues**; // y values

loopLimits = polygons << **Summary****(**Group**(** :Shape ID, :Part ID **)**, Invisible**)**;

ns = Maximum**(**Column**(**loopLimits, "Shape ID"**)** << **getValues****)**; // Number of shapes

np = Column**(**loopLimits, "Part ID"**)** << **getValues**; // Number of parts in each shape

Close**(**loopLimits, NoSave**)**;

For**(**s=**1**, s <= ns, s++, // Loop over shapes

For**(**p=**1**, p<=np**[**s**]**, p++, // Loop over parts

For**(**d=NRow**(**x**)**, d>=**1**, d--, // Loop over dots

// Is the dot inside the square that inscribes the polygon? (Relatively cheap!)

if **(**inShapePartRect**(**pMat, s, p, x**[**d**]**, y**[**d**])**,

// If so, see if it's actually in the polygon (Relatively expensive!)

if **(**inShapePart**(**pMat, s, p, x**[**d**]**, y**[**d**])**,

shapeName = Column**(**names, "Name"**)[**s**]**;

Print**(**"Point ("||Char**(**x**[**d**])**||", "||Char**(**y**[**d**])**||") found in Shape "||Char**(**s**)**||", Part "||Char**(**p**)**||", with Name "||shapeName||"."**)**;

// NB: As each dot is 'found', delete the corresponding entries in 'x' and'y' to try to speed things up

x**[**d**]** = **[]**; y**[**d**]** = **[]**

**)**;

**)**;

**)**;

**)**;

**)**;

Close**(**dots, NoSave**)**;

Close**(**polygons, NoSave**)**;

Close**(**names, NoSave**)**;

8 REPLIES

- Mark as New
- Bookmark
- Subscribe
- Subscribe to RSS Feed
- Get Direct Link
- Email to a Friend
- Report Inappropriate Content

I think you will need scripting. The 'InPolygon()' JSL function should be useful.

- Mark as New
- Bookmark
- Subscribe
- Subscribe to RSS Feed
- Get Direct Link
- Email to a Friend
- Report Inappropriate Content

- Mark as New
- Bookmark
- Subscribe
- Subscribe to RSS Feed
- Get Direct Link
- Email to a Friend
- Report Inappropriate Content

I would love to see a sample code if you get it to work.

- Mark as New
- Bookmark
- Subscribe
- Subscribe to RSS Feed
- Get Direct Link
- Email to a Friend
- Report Inappropriate Content

Hi All,

I have manage to get the script working. It takes each point and matches it with the polygon it is surrounded by to join the tables. In this case it adds the name of the state to each dot.

The script does its job but perhaps not in the most efficient way. Anny ideas for improved efficiency are welcome.

If you get to use it please share your experience.

Clear Globals**()**;

Names Default To Here**(** **1** **)**;

// open dots file - this includes the observation we want to update

dots = Open**(** "C:\Program Files (x86)\SAS\JMPSW\11\Samples\Data\Hurricanes.jmp" **)**;

// reduce the size of the data for the illustration

dots << **delete rows(** dots << **get rows where(** :Name**(** "Name and ID" **)** != "NOTNAMED_675" **)** **)**;

// open polygon source

polygons = Open**(** "C:\Program Files (x86)\SAS\JMPSW\11\Maps\US-State-XY.jmp", Private **)**;

// open data source - this one includes the information to update

data = Open**(** "C:\Program Files (x86)\SAS\JMPSW\11\Maps\US-State-Name.jmp", Private **)**;

// introduce the column with the attributes to be updated in the dots table

dots << **New Column(** "State", Character, Nominal **)**;

dots << **Set Label Columns(** :State **)**;

Wait**(** **1** **)**;

// extract the polygons into matrices to improve efficiency

XMATRIX = polygons:X << **get as matrix**; // x values of polygons

YMATRIX = polygons:Y << **get as matrix**; // y value of polygons

Start = Tick Seconds**()**;

// run through the dots across the polygons and check for matches.

For**(** i = **1**, i <= N Rows**(** dots **)**, i++, // for each dot

For**(** ii = **1**, ii <= Col Max**(** polygons:Shape ID **)**, ii++, // for each shape ID

ROWLISTII = polygons << **get rows where(** polygons:Shape ID == ii **)**; // get the rows for each Shape ID

For**(** iii = **1**, iii <= Max**(** Polygons:Part ID**[**ROWLISTII**]** **)**, iii++, // for each Part ID according to the number of Part IDs for each Shape ID

// get the rows for each polygon

rowsii = polygons << **get rows where(** And**(** polygons:Shape ID == ii, polygons:Part ID == iii **)** **)**;

// get the X values for each polygon

XMATRIXROWSII = XMATRIX**[**rowsii**]**;

// get the Y values for each polygon

YMATRIXROWSII = YMATRIX**[**rowsii**]**;

// compare the location of the dot with the polygon

result = In Polygon**(** dots:longitude**[**i**]**, dots:Latitude**[**i**]**, XMATRIXROWSII, YMATRIXROWSII **)**;

If**(** result == **1**, Show**(** i, ii, data:Name**[**ii**]** **)** **)**; // this will send the list of matching pairs to the log

If**(** result == **1**,

dots:State**[**i**]** = data:Name**[**ii**]**;// this will make the update of the attribute from the polygon information (data) to the dot (dots)

Break**()**; // this will make the third loop (iii) stop checking more polygons of the same Shape ID.

**)**;

**)**;

If**(** result == **1**, Break**()** **)**; // this will make the second loop (ii) stop checking for more Shape ID's for the same dot and move to the next dot in the list.

**)**

**)**;

End = Tick Seconds**()**;

Runtime = End - Start;

Show**(** runtime **)**;

// Wait( 1 );

dots << **Bivariate(**

Y**(** :Latitude **)**,

X**(** :Longitude **)**,

SendToReport**(**

Dispatch**(** **{}**, "1", ScaleBox, **{**Scale**(** "Geodesic" **)**, Min**(** -**94.0515072337543** **)**, Max**(** -**60.9484927662457** **)**, Inc**(** **5** **)**, Minor Ticks**(** **1** **)}** **)**,

Dispatch**(** **{}**, "2", ScaleBox, **{**Scale**(** "Geodesic" **)**, Min**(** **7.96034811422145** **)**, Max**(** **49.6893151267156** **)**, Inc**(** **5** **)**, Minor Ticks**(** **1** **)}** **)**,

Dispatch**(**

**{}**,

"Bivar Plot",

FrameBox,

**{**Frame Size**(** **449**, **566** **)**, Background Map**(** Boundaries**(** "US States" **)** **)**, Grid Line Order**(** **3** **)**, Reference Line Order**(** **4** **)**,

Row Legend**(**

State,

Color**(** **1** **)**,

Color Theme**(** "JMP Default" **)**,

Marker**(** **0** **)**,

Marker Theme**(** "" **)**,

Continuous Scale**(** **0** **)**,

Reverse Scale**(** **0** **)**,

Excluded Rows**(** **0** **)**

**)}**

**)**

**)**

**)**;

- Mark as New
- Bookmark
- Subscribe
- Subscribe to RSS Feed
- Get Direct Link
- Email to a Friend
- Report Inappropriate Content

Coincidentally, I just found time to take a quick look. I make no claims for improved efficiency, but only offer it for comparison purposes. I did no checking or testing. For the subset you are using, it runs almost instantly. Note the OS/X paths.

NamesDefaultToHere**(****1****)**;

ClearLog**()**;

// Open dots file - this includes the observation we want to update

dots = Open**(** "$SAMPLE_DATA/Hurricanes.jmp" **)**;

dots << **delete rows****(** dots << **get rows where****(** :Name**(** "Name and ID" **)** != "NOTNAMED_675" **)** **)**;

// Open polygon source

polygons = Open**(** "/Library/Application Support/JMP/12/Maps/US-State-XY.jmp" **)**;

// Open data source - this one includes the information to update

names = Open**(** "/Library/Application Support/JMP/12/Maps/US-State-Name.jmp" **)**;

// Given a 'polygon matrix' pMat, and a shapeID and PartID therein, determines if the point (x, y) lies in the polygon defined by this shape and part

inShapePart =

Function**({**pMat, shapeID, partID, x, y**}**, **{**Default Local**}**,

r = Loc**(**pMat**[****0**,**1****]** == shapeID & pMat**[****0**,**2****]** == partID**)**;

InPolygon**(**x, y, pMat**[**r,**3****]**, pMat**[**r,**4****])**;

**)**;

// Given a 'polygon matrix' pMat, and a shapeID and PartID therein, determines if the point (x, y) lies in the rectangle inscribed by this shape and part

inShapePartRect =

Function**({**pMat, shapeID, partID, x, y**}**, **{**Default Local**}**,

r = Loc**(**pMat**[****0**,**1****]** == shapeID & pMat**[****0**,**2****]** == partID**)**;

maxX = Maximum**(**pMat**[**r, **3****])**;

minX = Minimum**(**pMat**[**r, **3****])**;

maxY = Maximum**(**pMat**[**r, **4****])**;

minY = Minimum**(**pMat**[**r, **4****])**;

if**(** **(**x <= maxX**)** & **(**x >= minX**)** & **(**y <= maxY**)** & **(**y >= minY**)**, **1**, **0****)**;

**)**;

pMat = polygons << **getAsMatrix**; // 'Polygon matrix'

x = Column**(**dots, "Longitude"**)** << **getValues**; // x values

y = Column**(**dots, "Latitude"**)** << **getValues**; // y values

loopLimits = polygons << **Summary****(**Group**(** :Shape ID, :Part ID **)**, Invisible**)**;

ns = Maximum**(**Column**(**loopLimits, "Shape ID"**)** << **getValues****)**; // Number of shapes

np = Column**(**loopLimits, "Part ID"**)** << **getValues**; // Number of parts in each shape

Close**(**loopLimits, NoSave**)**;

For**(**s=**1**, s <= ns, s++, // Loop over shapes

For**(**p=**1**, p<=np**[**s**]**, p++, // Loop over parts

For**(**d=NRow**(**x**)**, d>=**1**, d--, // Loop over dots

// Is the dot inside the square that inscribes the polygon? (Relatively cheap!)

if **(**inShapePartRect**(**pMat, s, p, x**[**d**]**, y**[**d**])**,

// If so, see if it's actually in the polygon (Relatively expensive!)

if **(**inShapePart**(**pMat, s, p, x**[**d**]**, y**[**d**])**,

shapeName = Column**(**names, "Name"**)[**s**]**;

Print**(**"Point ("||Char**(**x**[**d**])**||", "||Char**(**y**[**d**])**||") found in Shape "||Char**(**s**)**||", Part "||Char**(**p**)**||", with Name "||shapeName||"."**)**;

// NB: As each dot is 'found', delete the corresponding entries in 'x' and'y' to try to speed things up

x**[**d**]** = **[]**; y**[**d**]** = **[]**

**)**;

**)**;

**)**;

**)**;

**)**;

Close**(**dots, NoSave**)**;

Close**(**polygons, NoSave**)**;

Close**(**names, NoSave**)**;

- Mark as New
- Bookmark
- Subscribe
- Subscribe to RSS Feed
- Get Direct Link
- Email to a Friend
- Report Inappropriate Content

thank you Ian@JMP,

this is way better than my approach and much faster.

while producing the loops in my script i noticed that the XY table is not perfectly clean and your approach is also robust against that.(shape ID 10 has part ID 1,3,4,5 - skipping 2 ).

ron

Highlighted
##
##### Re: join tables based on location

- Mark as New
- Bookmark
- Subscribe
- Subscribe to RSS Feed
- Get Direct Link
- Email to a Friend
- Report Inappropriate Content

8 and a half hours later the whole file of hurricanes was joined. since most of the dots are in the sea the script took some time to verify that each one of them was not in any of the 52 states (84 polygons).

- Mark as New
- Bookmark
- Subscribe
- Subscribe to RSS Feed
- Get Direct Link
- Email to a Friend
- Report Inappropriate Content

To further speed the script up, you can check if the point is in "America" square first. Appending onto Ian's script

inAmericaRect =

Function({pMat, x, y}, {Default Local},

maxX = Maximum(pMat[0, 3]);

minX = Minimum(pMat[0, 3]);

maxY = Maximum(pMat[0, 4]);

minY = Minimum(pMat[0, 4]);

if( (x <= maxX) & (x >= minX) & (y <= maxY) & (y >= minY), 1, 0);

);

...

// Is the dot inside the Square of America?

if (inAmericaRect(pMat, x

Of course, given Puerto Rico, this could potentially slow down the script by adding an extra if statement. I still feel there is enough data outside to warrant it. But to fix that, you could simply split out Puerto Rico or other islands and add an addition inPuertoRicoRect function. You could do additional filtering for other countries using world-name.jmp and world-xy.jmp. Or you could use the coastline-Name.jmp and coastline-xy.jmp to first determine if it's on land. But, I'm sure at that point your script will be going outside the scope of the project.