How to use a custom format

Format specification

For this example we will use the following (made up) example file format.

TIN
NAME <name>
COLOR <r> <g> <b> <a>
NVERTEX <number_of_vertices>
<x_coord> <y_coord> <z_coord>
...
<x_coord> <y_coord> <z_coord>
NTRIANGLE <number_of_triangles>
<v1_idx> <v2_idx> <v1_idx>
...
<v1_idx> <v2_idx> <v1_idx>
END

Somre more rules:

  • All fields are required and have to appear in the order as specified
  • Fields are in all capital letters
  • x,y,z coords are floating point values (IEEE 754 double-precision)
  • The first point has the index 0, the last point has the index (num_points - 1)
  • Triangles are defined by its 3 vertex indices
  • v1,v2,v3 idx are integers up to 32 bit (min value: 0, max value: 4294967295)
  • r,g,b,a are integers values between 0 and 255
  • Files can contain exactly one TIN

Example:

TIN
NAME topographie
COLOR 255 0 0 0
NVERTEX 4
0 0 0
1 0 0
0 0.5 0
1 1 0
NTRIANGLE 2
0 1 2
2 1 3
END

Code

Imports and some helper functions

First we start with the imports and some small helper functions.

We also define some simple helper types for Point, Triangle and Color.

""" Example implementation of how to use GSTPy with a custom geometry format"""
from math import floor
from collections import namedtuple
import GSTPy


def get_gst_connection():
    user = "gstpy_user"
    password = "gstpy_user_pw"
    host = "localhost"
    port = 5432
    db = "gstpy"
    return GSTPy.get_pg_connection(user, password, host, port, db)


def float_to_int_color_value(float_val):
    """
    return the integer color value for a float color value

    Parameters:
        float_val(float): float color (value between 0 and 1)

    Returns:
        int: the integer color value (value between 0 and 255)
    """
    return int(floor(float_val * 255))


def int_to_float_color_value(int_val):
    """
    return the float color value for a int color value

    Parameters:
        int_val(int): int color (value between 0 and 255)

    Returns:
        int: the float color value (value between 0 and 1)
    """
    return float(int_val) / 255.


# we use some simple named tuples for Color, Point and Triangle

Color = namedtuple('Color', ['r', 'g', 'b', 'a'])

Point = namedtuple('Point', ['x', 'y', 'z'])

Triangle = namedtuple('Triangle', ['v1', 'v2', 'v3'])

TIN representation

The TIN representation is rather simple. It has a name, a color, a list of vertices and a list of triangles.

It also has a method to get a GSTPy.GeometryBuilder instance from the tin data.

Finally it has a method to read/write a TIN from/to a file.

class Tin(object):
    """ TIN representation """

    def __init__(self):
        self.name = "default"
        self.color = Color(0, 0, 0, 0)
        self.vertices = []
        self.triangles = []

    def as_geometry_builder(self):
        """ encodes the TIN representation in a GSTPy.GeometryBuilder"""
        gb = GSTPy.GeometryBuilder()
        gb.create_triangle_net3()
        gb.set_name(self.name)
        gb.set_color(
            GSTPy.Color(
                int_to_float_color_value(self.color.r),
                int_to_float_color_value(self.color.g),
                int_to_float_color_value(self.color.b)))
        gb.set_transparency(int_to_float_color_value(255 - self.color.a))
        for point in self.vertices:
            gb.add_point(GSTPy.RawPoint3(point.x, point.y, point.z))

        for triangle in self.triangles:
            gb.add_triangle(
                GSTPy.IdxTriangle(triangle.v1, triangle.v2, triangle.v3))
        return gb

    def from_file(self, tin_file):
        """ reads a TIN from tin_file """

        def get_line_from_tin_file():
            """ 
            Returns a line from the tin file
            """
            line = tin_file.readline()
            if line == "":
                raise RuntimeError("Unexpected end of file")
            line = line.rstrip('\n')
            return line

        def get_line_tokens(first_token):
            """ 
            Tests if first token is the expected first token and then
            returns the remaining tokens of the line
            """
            line = get_line_from_tin_file()
            tokens = line.split(" ")
            if tokens[0] != first_token:
                raise RuntimeError("Expected {} line, but got: {}".format(
                    first_token, line))
            return tokens[1:]

        line = get_line_from_tin_file()
        if line != "TIN":
            raise RuntimeError("Expected TIN line, but got: {}".format(line))
        tokens = get_line_tokens("NAME")
        self.name = tokens[0]
        tokens = get_line_tokens("COLOR")
        self.color = Color(
            int(tokens[0]), int(tokens[1]), int(tokens[2]), int(tokens[3]))
        tokens = get_line_tokens("NVERTEX")
        num_vertices = int(tokens[0])
        for i in range(0, num_vertices):
            line = get_line_from_tin_file()
            tokens = line.split(" ")
            self.vertices.append(
                Point(float(tokens[0]), float(tokens[1]), float(tokens[2])))
        tokens = get_line_tokens("NTRIANGLE")
        num_triangles = int(tokens[0])
        for i in range(0, num_triangles):
            line = get_line_from_tin_file()
            tokens = line.split(" ")
            self.triangles.append(
                Triangle(int(tokens[0]), int(tokens[1]), int(tokens[2])))
        line = get_line_from_tin_file()
        if line != "END":
            raise RuntimeError("Expected END line, but got: {}".format(line))

    def to_file(self, tin_file):
        """ writes the TIN to tin_file """
        tin_file.write("TIN\n")
        tin_file.write("NAME {}\n".format(self.name))
        tin_file.write("COLOR {} {} {} {}\n".format(
            self.color.r, self.color.g, self.color.b, self.color.a))
        tin_file.write("NVERTEX {}\n".format(len(self.vertices)))
        for vertex in self.vertices:
            tin_file.write("{} {} {}\n".format(vertex.x, vertex.y, vertex.z))
        tin_file.write("NTRIANGLE {}\n".format(len(self.triangles)))
        for triangle in self.triangles:
            tin_file.write("{} {} {}\n".format(triangle.v1, triangle.v2,
                                               triangle.v3))
        tin_file.write("END\n")

TIN IParsingActions

TinReader is used to retrieve a TIN representation from GSTPy.NetworkInterface.get_feature_custom_format().

class TinReader(GSTPy.IParsingActions):
    """ Get TIN representation from GSTPy get_feature_custom format"""

    def __init__(self):
        super(TinReader, self).__init__()

        self.tin = Tin()

    def set_name(self, name):
        """
        sets the name for the tin

        Parameters:
            name(str): the name of the GST feature
        """
        self.tin.name = name

    def set_color(self, color):
        """
        sets the color for the tin

        Note:
            GSTPy.color has only r,g,b, the values are floats between 0..1

        Parameters:
            color(GSTPy.color): the color of the GST feature
        """
        self.tin.color = self.tin.color._replace(
            r=float_to_int_color_value(color.r),
            g=float_to_int_color_value(color.g),
            b=float_to_int_color_value(color.b))

    def set_transparency(self, transparency):
        """
        sets the transparency for the tin

        Note:
            value between 0..1

        Parameters:
            transparency(float): the transparency of the GST feature
        """
        self.tin.color = self.tin.color._replace(
            a=float_to_int_color_value(1 - transparency))

    def add_point(self, point):
        """
        adds a point to the tin

        Note:
            GSTPy.RawPoint3 has x,y,z as floats.
            The point indices are in the same order as calls to add_point,
            starting with index=0.
            The v1,v2,v3 values in GSTPy.IdxTriangle reference those indices.

        Parameters:
            point(GSTPy.RawPoint3): a point of the GST feature
        """
        self.tin.vertices.append(Point(point.x, point.y, point.z))

    def add_triangle(self, triangle):
        """
        adds a triangle to the tin

        Note:
            GSTPy.IdxTriangle has v1,v3,v3 as integers.
            The v1,v2,v3 values in GSTPy.IdxTriangle reference the indices
            from add_point.

            For example, the triangle (2, 1, 3) references the points from the 
            third, second and fourth add_point call (in that order).

        Parameters:
            triangle(GSTPy.IdxTriangle): a triangle of the GST feature
        """
        self.tin.triangles.append(
            Triangle(triangle.v1, triangle.v2, triangle.v3))

Usage in action

Here we see it all used in action.

See below for the the output of the .tin files.

ni = get_gst_connection()
tin = Tin()
# read the TTIN from a file
with open(r"test_tin_file.tin", "r") as tin_file:
    tin.from_file(tin_file)
# get a GeometryBuilder for our read TIN and upload it to GST
tin_gb = tin.as_geometry_builder()
fc = ni.create_feature_class("TIN", GSTPy.GeometryTypes.Tin3)
f = ni.upload_feature(tin_gb, fc)
# use TinReader to download the TIN format from GST
tin_cb = TinReader()
ni.get_feature_custom_format(f, tin_cb)
tin = tin_cb.tin
# write the donwloaded TIN to a file
with open(r"test_tin_file_download.tin", "w") as tin_file:
    tin_cb.tin.to_file(tin_file)
# append a new 5th vertex (idx=4)
tin.vertices.append(Point(0, 0, 1))
# append a new triangle
tin.triangles.append(Triangle(0, 1, 4))
# get an updated GeometryBuilder
tin_gb = tin.as_geometry_builder()
# update the now changed tin
ni.instant_update_feature(tin_gb, f)
# update the now changed tin
tin_cb = TinReader()
# download the updated tin
ni.get_feature_custom_format(f, tin_cb)
tin = tin_cb.tin
with open(r"test_tin_file_update.tin", "w") as tin_file:
    tin_cb.tin.to_file(tin_file)
ni.delete_feature(f)
ni.delete_feature_class(fc)

Full code sample

Python source code

""" Example implementation of how to use GSTPy with a custom geometry format"""
from math import floor
from collections import namedtuple
import GSTPy


def get_gst_connection():
    user = "gstpy_user"
    password = "gstpy_user_pw"
    host = "localhost"
    port = 5432
    db = "gstpy"
    return GSTPy.get_pg_connection(user, password, host, port, db)


def float_to_int_color_value(float_val):
    """
    return the integer color value for a float color value

    Parameters:
        float_val(float): float color (value between 0 and 1)

    Returns:
        int: the integer color value (value between 0 and 255)
    """
    return int(floor(float_val * 255))


def int_to_float_color_value(int_val):
    """
    return the float color value for a int color value

    Parameters:
        int_val(int): int color (value between 0 and 255)

    Returns:
        int: the float color value (value between 0 and 1)
    """
    return float(int_val) / 255.


# we use some simple named tuples for Color, Point and Triangle

Color = namedtuple('Color', ['r', 'g', 'b', 'a'])

Point = namedtuple('Point', ['x', 'y', 'z'])

Triangle = namedtuple('Triangle', ['v1', 'v2', 'v3'])


class TinReader(GSTPy.IParsingActions):
    """ Get TIN representation from GSTPy get_feature_custom format"""

    def __init__(self):
        super(TinReader, self).__init__()

        self.tin = Tin()

    def set_name(self, name):
        """
        sets the name for the tin

        Parameters:
            name(str): the name of the GST feature
        """
        self.tin.name = name

    def set_color(self, color):
        """
        sets the color for the tin

        Note:
            GSTPy.color has only r,g,b, the values are floats between 0..1

        Parameters:
            color(GSTPy.color): the color of the GST feature
        """
        self.tin.color = self.tin.color._replace(
            r=float_to_int_color_value(color.r),
            g=float_to_int_color_value(color.g),
            b=float_to_int_color_value(color.b))

    def set_transparency(self, transparency):
        """
        sets the transparency for the tin

        Note:
            value between 0..1

        Parameters:
            transparency(float): the transparency of the GST feature
        """
        self.tin.color = self.tin.color._replace(
            a=float_to_int_color_value(1 - transparency))

    def add_point(self, point):
        """
        adds a point to the tin

        Note:
            GSTPy.RawPoint3 has x,y,z as floats.
            The point indices are in the same order as calls to add_point,
            starting with index=0.
            The v1,v2,v3 values in GSTPy.IdxTriangle reference those indices.

        Parameters:
            point(GSTPy.RawPoint3): a point of the GST feature
        """
        self.tin.vertices.append(Point(point.x, point.y, point.z))

    def add_triangle(self, triangle):
        """
        adds a triangle to the tin

        Note:
            GSTPy.IdxTriangle has v1,v3,v3 as integers.
            The v1,v2,v3 values in GSTPy.IdxTriangle reference the indices
            from add_point.

            For example, the triangle (2, 1, 3) references the points from the 
            third, second and fourth add_point call (in that order).

        Parameters:
            triangle(GSTPy.IdxTriangle): a triangle of the GST feature
        """
        self.tin.triangles.append(
            Triangle(triangle.v1, triangle.v2, triangle.v3))


class Tin(object):
    """ TIN representation """

    def __init__(self):
        self.name = "default"
        self.color = Color(0, 0, 0, 0)
        self.vertices = []
        self.triangles = []

    def as_geometry_builder(self):
        """ encodes the TIN representation in a GSTPy.GeometryBuilder"""
        gb = GSTPy.GeometryBuilder()
        gb.create_triangle_net3()
        gb.set_name(self.name)
        gb.set_color(
            GSTPy.Color(
                int_to_float_color_value(self.color.r),
                int_to_float_color_value(self.color.g),
                int_to_float_color_value(self.color.b)))
        gb.set_transparency(int_to_float_color_value(255 - self.color.a))
        for point in self.vertices:
            gb.add_point(GSTPy.RawPoint3(point.x, point.y, point.z))

        for triangle in self.triangles:
            gb.add_triangle(
                GSTPy.IdxTriangle(triangle.v1, triangle.v2, triangle.v3))
        return gb

    def from_file(self, tin_file):
        """ reads a TIN from tin_file """

        def get_line_from_tin_file():
            """ 
            Returns a line from the tin file
            """
            line = tin_file.readline()
            if line == "":
                raise RuntimeError("Unexpected end of file")
            line = line.rstrip('\n')
            return line

        def get_line_tokens(first_token):
            """ 
            Tests if first token is the expected first token and then
            returns the remaining tokens of the line
            """
            line = get_line_from_tin_file()
            tokens = line.split(" ")
            if tokens[0] != first_token:
                raise RuntimeError("Expected {} line, but got: {}".format(
                    first_token, line))
            return tokens[1:]

        line = get_line_from_tin_file()
        if line != "TIN":
            raise RuntimeError("Expected TIN line, but got: {}".format(line))
        tokens = get_line_tokens("NAME")
        self.name = tokens[0]
        tokens = get_line_tokens("COLOR")
        self.color = Color(
            int(tokens[0]), int(tokens[1]), int(tokens[2]), int(tokens[3]))
        tokens = get_line_tokens("NVERTEX")
        num_vertices = int(tokens[0])
        for i in range(0, num_vertices):
            line = get_line_from_tin_file()
            tokens = line.split(" ")
            self.vertices.append(
                Point(float(tokens[0]), float(tokens[1]), float(tokens[2])))
        tokens = get_line_tokens("NTRIANGLE")
        num_triangles = int(tokens[0])
        for i in range(0, num_triangles):
            line = get_line_from_tin_file()
            tokens = line.split(" ")
            self.triangles.append(
                Triangle(int(tokens[0]), int(tokens[1]), int(tokens[2])))
        line = get_line_from_tin_file()
        if line != "END":
            raise RuntimeError("Expected END line, but got: {}".format(line))

    def to_file(self, tin_file):
        """ writes the TIN to tin_file """
        tin_file.write("TIN\n")
        tin_file.write("NAME {}\n".format(self.name))
        tin_file.write("COLOR {} {} {} {}\n".format(
            self.color.r, self.color.g, self.color.b, self.color.a))
        tin_file.write("NVERTEX {}\n".format(len(self.vertices)))
        for vertex in self.vertices:
            tin_file.write("{} {} {}\n".format(vertex.x, vertex.y, vertex.z))
        tin_file.write("NTRIANGLE {}\n".format(len(self.triangles)))
        for triangle in self.triangles:
            tin_file.write("{} {} {}\n".format(triangle.v1, triangle.v2,
                                               triangle.v3))
        tin_file.write("END\n")


def main():
    ni = get_gst_connection()
    tin = Tin()
    # read the TTIN from a file
    with open(r"test_tin_file.tin", "r") as tin_file:
        tin.from_file(tin_file)
    # get a GeometryBuilder for our read TIN and upload it to GST
    tin_gb = tin.as_geometry_builder()
    fc = ni.create_feature_class("TIN", GSTPy.GeometryTypes.Tin3)
    f = ni.upload_feature(tin_gb, fc)
    # use TinReader to download the TIN format from GST
    tin_cb = TinReader()
    ni.get_feature_custom_format(f, tin_cb)
    tin = tin_cb.tin
    # write the donwloaded TIN to a file
    with open(r"test_tin_file_download.tin", "w") as tin_file:
        tin_cb.tin.to_file(tin_file)
    # append a new 5th vertex (idx=4)
    tin.vertices.append(Point(0, 0, 1))
    # append a new triangle
    tin.triangles.append(Triangle(0, 1, 4))
    # get an updated GeometryBuilder
    tin_gb = tin.as_geometry_builder()
    # update the now changed tin
    ni.instant_update_feature(tin_gb, f)
    # update the now changed tin
    tin_cb = TinReader()
    # download the updated tin
    ni.get_feature_custom_format(f, tin_cb)
    tin = tin_cb.tin
    with open(r"test_tin_file_update.tin", "w") as tin_file:
        tin_cb.tin.to_file(tin_file)
    ni.delete_feature(f)
    ni.delete_feature_class(fc)


if __name__ == "__main__":
    main()

.tin files

test_tin_file.tin
TIN
NAME topographie
COLOR 255 0 0 0
NVERTEX 4
0 0 0
1 0 0
0 0.5 0
1 1 0
NTRIANGLE 2
0 1 2
2 1 3
END
test_tin_file_download.tin
TIN
NAME topographie
COLOR 255 0 0 0
NVERTEX 4
0.0 0.0 0.0
1.0 0.0 0.0
0.0 0.5 0.0
1.0 1.0 0.0
NTRIANGLE 2
0 1 2
2 1 3
END
test_tin_file_update.tin
TIN
NAME topographie
COLOR 255 0 0 0
NVERTEX 5
0.0 0.0 0.0
1.0 0.0 0.0
0.0 0.5 0.0
1.0 1.0 0.0
0.0 0.0 1.0
NTRIANGLE 3
0 1 2
2 1 3
0 1 4
END