ScriptSpot

Forum topic · Scripts Wanted

Creating an ASCII Text file for ArcGIS from existing Terrain

By todd21st · 2012-09-24

Description

What I need is a MAXScript that will sample an existing terrain on a grid

that is n columns by n rows with a specified interval for each. For

example: 10 columns by 10 rows with 10m between each column and 10m

between each row.

All the script needs to do is start with a point in the upper left hand

corner of the array, project a test ray in the negative Z direction, once

that ray intersects with the terrain below write that elevation along

with the point number to a .txt file and then move the point to the next

position to the right (based on the spacing interval), renumber the

point, and repeat the same output process.

Once the point gets to the end of the column it needs to go back to the

far left and begin again on the next row, keeping the numbering

consistent. The numbering for the array would begin with "Point01" in

the upper left hand corner and go from left to right until it reaches the

bottom right of the array. In this example of a 10 x 10 array the last

point number would be "Point100" which would be the last point on the far

right of the last row.

For a small array I could create points in 3dsMax and use the 'Move to

Surface' script, then export the positions myself to a text file but the

arrays I'm dealing with for this particular project are thousands of

columns x thousands of rows. This results in millions of points in the

array. Figured if I could get someone to help me out with a script that

would move, rename, and output one point that traveled around the terrain

in an array fasion, it would be an easier approach to obtaining the

elevation data in a text format.

Comments (14)

miauu · 2012-09-26

Check your email.

todd21st · 2012-09-26

I get it now. I looked at the file again and saw that in the areas where there is no terrain hit, the script still creates the point. Can we get it to print the output for "No data value" like the rest of the points? Print the x value, y value, and then -999 (or whatever is specified in the UI) for the z for those points?

miauu · 2012-09-26

The points are crated just to show you where they are in the terrain. I expected that you will remove this line(that creates the points) from the script, because creating all those points slow down the performance. I will add checkbox to allows you to create or not points in both places - terain and "nothing". This covers question 2) In the infoFile all points are written - you can see for example - point234 -999 this ie "no data point" - the point that touching the terrain. Also will include the "no data point" - question 1)
The script do not use those points. It create them after the rayHit is executed. So there is no need of them, but they are a good visual representation of what happens.
[quote] Also, how difficult would it be to still be able to specify the first start point of the array like you could in the first script? [/quote]

In the first script you can't define the starting point. When you look your scene in TOP viewport, the starting point is at top left part of your selection of objects.

[quote] And can we format the data to print as follows: PointNumber,x,y,z [/quote]
Yes. Will add this.

todd21st · 2012-09-26

Also, how difficult would it be to still be able to specify the first start point of the array like you could in the first script?

And can we format the data to print as follows:
PointNumber,x,y,z

with no brackets?

I know I'm asking for a lot and I do appreciate your patience.

todd21st · 2012-09-26

Very very cool! This script is really turning out to be amazing! I love the ArcGIS_Exporter_v11.ms....looks like we're getting there. Two things though:

1) When creating the array I need each column & each row to have the same number of points respectively like the image below shows...

http://call21st.com/goofy7012/screenShot.png

Where there are no terrain tiles for the points to be dropped on, there still needs to be points created and just placed at the "No data value" elevation. This way each row and column will have the same number of points.

2) Is it possible to drop a line of code in to not create the points? This way once I get the array set up and have it working like I want, I can push the button to not create the points for the final and larger array. I'd like this feature since originally you said Max could do this function without having to generate actual geometry, points, etc. I think this would be a really cool option.

Looks great so far just these couple of things and I think we'll be there. Let me know what you think.

Thanks A Million!

Todd Davis

miauu · 2012-09-26

Check your email.

Graph · 2012-09-25

well here's a faster version of it:


(
	
	local theTerrain= $TerrainMesh

	local nRows = 50
	local nCols = 50
	
	local projectionDir = [0,0,-1]
	
	local borderMargin = 0.0001
	
	/********************/
	/********************/

	local rm = RayMeshGridIntersect() 
	rm.Initialize 10 
	rm.addNode theTerrain 
	rm.buildGrid()

	local terrainBBSize = theTerrain.max-theTerrain.min - [borderMargin*2,borderMargin*2,0]
	
	local stepSizeX  = terrainBBSize.x/(nRows-1) as float
	local stepSizeY = terrainBBSize.x/(nCols-1) as float
	
	local minX = theTerrain.min.x+borderMargin
	local minY = theTerrain.min.y+borderMargin
	local maxZ = theTerrain.max.z
	
	local pointArr = for y = 0 to nCols-1 collect
	(
		for x = 0 to nRows-1 collect
		(
			local thePos = point3 (minX+x*stepSizeX) (minY+y*stepSizeY) maxZ
			local theHitsCount = rm.intersectRay thePos projectionDir false
			
			if theHitsCount > 0 then
			(
				local theIndex = rm.getClosestHit()
				local dist = rm.getHitDist theIndex
				
				thePos.z -= dist
				thePos
			)
			else
			(
				thePos
			)
		)
	)

	print pointArr

	for x in pointArr do for y in x do point pos:y wireColor:orange;
	
)

as it's a static object/collection you can use the RayMeshGridIntersect class wich is significantly faster, just something to note for the future :)

todd21st · 2012-09-25

Many thanks my friend. I'll be sure that our company makes a donation for your efforts.

miauu · 2012-09-25

I will do it later this night. :)

todd21st · 2012-09-25

I do appreciate all your help and once my boss returns from a business trip I'm sure I can get him to make a donation on your web page.

todd21st · 2012-09-25

Thanks. As I'm fairly a novice to this would you mind modifying the code to include a loop? That way I can just select all objects that make up my terrain and run the script. Only reason I ask is because I'm dealing with upwards of 80,000 tiles to create the terrain. Also, would it be too much to ask to drop a line of code in there for a default elevation value to assign to a point in case the point happens to not intersect terrain below? Say a default no data value of -999?

miauu · 2012-09-25

If the selection is more than one object, then $ holds all seleced objects. The intersectRay require one node and one ray.
From maxscript help file



intersectRay <node> <ray>
Computes the closest intersection of the ray and the surface of the given node

Save your scene, attach all tiles to one object and use the script. Then reload original scene. Or do loop so all tiles to be processed.

todd21st · 2012-09-25

WOW! This is absolutely amazing!! What a great script. This does just about everything I wanted it to do. Thank you very very much!

Question: In my Max scene my terrain is a series of tiles. So when executing the script I will need to select my terrain manually and tell the script that the local z node will be whatever is in the selection. When I modify the script to read...

local z_node = $

it highlights and complains about this line...

local inteersectionPoint = (intersectRay z_node testRay).pos

any ideas why?

Hope this will help

miauu · 2012-09-24

You do not need a real point(helper or some other object) to get the point of the arra.
See the code below. The Plane and the Point helper are created just to show you the name and the position of the points in the array. Remove them from the code if you want to use it. The positions will be printed in the listener. You know what to uncomment to store the values to the file. Execute the code in top viewport, since I don't know where is the top left for you.


(
	local z_node = plane pos:[0,0,-100] width:100 length:100
	
	out_name = ((GetDir #export)+"/terrainInfo.txt")
	out_file = createfile out_name	
	
	columns = 5	--	the amount of the columns
	rows = 5		--	the amount of the rows
	stepX = 10		--	ajust this to get real 10 m or any other value
	stepY = 10
	pos = [0,0,0]	--	the pos for point01
	cnt = 0	--	this will count the points and will be used to get the proper names 
	with undo off	--	will increase the speed
	(
		with redraw off		--	will increase the speed
		(			
			for i = (columns-1) to 0 by -1 do
			(
				for j = 0 to (rows-1) do
				(
					cnt += 1
					local pos = [(j*stepX),(i*stepY),0]
					
					--	create point in proper plces
					local p = point pos:pos size:5
					
					--	find trhe intersection here
					local testRay = ray pos [0,0,-1]
					local nodeMaxZ = z_node.max.z
					testRay.pos.z = nodeMaxZ + 0.0001 * abs nodeMaxZ
					local inteersectionPoint = (intersectRay z_node testRay).pos
					
					--	move the point 
					p.pos = inteersectionPoint
					
					-- print in the listener(uncommeent the to:out_file to store to file)
					format "% %\n" ("Point"+(cnt as string))  inteersectionPoint --to:out_file
				)
			)
		)
	)
	close out_file
)