view pkg/controllers/cross.go @ 935:430d52c2f6ef

client: move layer isolines to be drawn at the top * Move layer isolones to be drawn last (and thus being "on top") so that the bottleneck (position) layer will not interfere that much with the colours. It also allows to set a white background with high opacity on the bottleneck polygon in order to get highly visible isolines.
author Bernhard Reiter <bernhard@intevation.de>
date Mon, 08 Oct 2018 17:20:42 +0200
parents 03e966b71a88
children a244b18cb916
line wrap: on
line source

package controllers

import (
	"context"
	"database/sql"
	"fmt"
	"log"
	"net/http"
	"time"

	"gemma.intevation.de/gemma/pkg/models"
	"gemma.intevation.de/gemma/pkg/octree"
)

const WGS84 = 4326

func reproject(
	rp *models.Reprojector,
	src models.GeoJSONLineCoordinates,
	ctx context.Context,
) (models.GeoJSONLineCoordinates, error) {

	dst := make(models.GeoJSONLineCoordinates, len(src))
	for i, s := range src {
		var err error
		if dst[i].Lat, dst[i].Lon, err = rp.Reproject(
			s.Lat, s.Lon, ctx,
		); err != nil {
			return nil, err
		}
	}
	return dst, nil
}

func projectBack(
	rp *models.Reprojector,
	lines octree.MultiLineStringZ,
	ctx context.Context,
) (models.GeoJSONMultiLineCoordinatesZ, error) {

	out := make(models.GeoJSONMultiLineCoordinatesZ, len(lines))

	for i, segment := range lines {

		coords := make(models.GeoJSONLineCoordinatesZ, len(segment))
		out[i] = coords
		for j, v := range segment {
			lat, lon, err := rp.Reproject(v.X, v.Y, ctx)
			if err != nil {
				return nil, err
			}
			coords[j] = models.GeoJSONCoordinateZ{Lat: lat, Lon: lon, Z: v.Z}
		}
	}
	return out, nil
}

const clipSQL = `
SELECT ST_AsBinary(ST_Transform(ST_Multi(ST_Intersection(
  ST_Transform(area::geometry, $1::integer),
  ST_GeomFromWKB($2, $1::integer))), 4326))
FROM waterway.sounding_results
WHERE bottleneck_id = $3 AND date_info = $4::date`

func clipAgainstArea(
	line octree.MultiLineStringZ,
	epsg uint32,
	bottleneck string,
	dateInfo time.Time,
	conn *sql.Conn,
	ctx context.Context,
) (models.GeoJSONMultiLineCoordinatesZ, error) {

	var mls models.GeoJSONMultiLineCoordinatesZ
	err := conn.QueryRowContext(
		ctx, clipSQL,
		epsg, line.AsWKB(),
		bottleneck, dateInfo,
	).Scan(&mls)

	return mls, err
}

func crossSection(
	input interface{},
	req *http.Request,
	conn *sql.Conn,
) (jr JSONResult, err error) {

	csi := input.(*models.CrossSectionInput)

	start := time.Now()

	tree, err := octree.Cache.Get(
		csi.Properties.Bottleneck, csi.Properties.Date.Time,
		conn, req.Context())

	log.Printf("loading octree took: %s\n", time.Since(start))
	if err != nil {
		return
	}

	if tree == nil {
		err = JSONError{
			Code: http.StatusNotFound,
			Message: fmt.Sprintf("Cannot find survey for %s/%s.",
				csi.Properties.Bottleneck,
				csi.Properties.Date.Time),
		}
		return
	}

	// The coordinate system of the octree is an UTM projection.
	// The input coordinates are in WGS84.
	// So we need to reproject them.

	start = time.Now()

	var rp *models.Reprojector
	if rp, err = models.NewReprojector(
		conn, req.Context(),
		WGS84, tree.EPSG,
	); err != nil {
		return
	}
	defer rp.Close()

	coords, err := reproject(rp, csi.Geometry.Coordinates, req.Context())

	log.Printf("transforming input coords took: %s\n", time.Since(start))
	if err != nil {
		return
	}

	start = time.Now()

	var segments octree.MultiLineStringZ

	for i := 0; i < len(coords)-1; i++ {
		c1 := &coords[i]
		c2 := &coords[i+1]

		verticalLine := octree.NewVerticalLine(c1.Lat, c1.Lon, c2.Lat, c2.Lon)

		var line octree.MultiLineStringZ
		tree.Vertical(c1.Lat, c1.Lon, c2.Lat, c2.Lon, func(t *octree.Triangle) {
			if ls := verticalLine.Intersection(t); len(ls) > 0 {
				line = append(line, ls)
			}
		})

		if len(line) > 0 {
			log.Printf("line length: %d\n", len(line))
			// They are all on the segment (c1.Lat, c1.Lon) - (c2.Lat, c2.Lon).
			// Sort them by project them on this line.
			joined := line.JoinOnLine(c1.Lat, c1.Lon, c2.Lat, c2.Lon)
			log.Printf("joined length: %d\n", len(joined))
			segments = append(segments, joined...)
		}

	}
	log.Printf("octree traversal took: %s\n", time.Since(start))

	start = time.Now()

	var joined models.GeoJSONMultiLineCoordinatesZ
	joined, err = clipAgainstArea(
		segments, tree.EPSG,
		csi.Properties.Bottleneck, csi.Properties.Date.Time,
		conn, req.Context(),
	)

	log.Printf("clipping and projecting back took: %s\n", time.Since(start))
	if err != nil {
		return
	}

	jr = JSONResult{
		Result: &models.CrossSectionOutput{
			Type: "Feature",
			Geometry: models.CrossSectionOutputGeometry{
				Type:        "MultiLineString",
				Coordinates: joined,
			},
			Properties: map[string]interface{}{},
		},
	}

	return
}