view pkg/controllers/shapestretches.go @ 4307:e5a831ecd557

Started to write multi polygon into shape file. [WIP]
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Mon, 02 Sep 2019 18:38:27 +0200
parents 0f467a839fe2
children 2c83d32bdd1b
line wrap: on
line source

// This is Free Software under GNU Affero General Public License v >= 3.0
// without warranty, see README.md and license for details.
//
// SPDX-License-Identifier: AGPL-3.0-or-later
// License-Filename: LICENSES/AGPL-3.0.txt
//
// Copyright (C) 2019 by via donau
//   – Österreichische Wasserstraßen-Gesellschaft mbH
// Software engineering by Intevation GmbH
//
// Author(s):
//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>

package controllers

import (
	"archive/zip"
	"database/sql"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"github.com/gorilla/mux"
	"github.com/jonas-p/go-shp"

	"gemma.intevation.de/gemma/pkg/config"
	"gemma.intevation.de/gemma/pkg/middleware"
	"gemma.intevation.de/gemma/pkg/wkb"
)

const (
	selectStretchSQL = `
SELECT
  id,
  isrs_asText(lower(stretch)),
  isrs_asText(upper(stretch)),
  ST_AsBinary(area::geometry),
  objnam,
  nobjnam,
  date_info,
  source_organization
FROM waterway.stretches WHERE
  staging_done AND
  name = $1`

	selectStretchCountriesSQL = `
SELECT country_code FROM waterway.stretch_countries
WHERE stretches_id = $1
ORDER BY country_code`
)

func flattenPoints(mp wkb.MultiPolygonGeom) []shp.Point {

	var n int
	for _, p := range mp {
		for _, r := range p {
			n += len(r)
		}
	}

	ps := make([]shp.Point, n)
	var i int
	for _, p := range mp {
		for _, r := range p {
			for _, v := range r {
				ps[i].X = v.X
				ps[i].Y = v.Y
				i++
			}
		}
	}

	log.Printf("info: total number points: %d\n", n)

	return ps
}

func asShpPolygon(mp wkb.MultiPolygonGeom) *shp.Polygon {

	points := flattenPoints(mp)

	p := &shp.Polygon{}

	var numParts int
	for _, pl := range mp {
		numParts += len(pl)
	}

	log.Printf("info: number parts: %d\n", numParts)

	p.NumParts = int32(numParts)
	p.NumPoints = int32(len(points))
	p.Parts = make([]int32, numParts)

	var marker int32

	for i, pl := range mp {
		p.Parts[i] = marker
		marker += int32(len(pl))
	}

	p.Points = points
	p.Box = shp.BBoxFromPoints(points)
	return p
}

func stretchShapeDownload(rw http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	name := vars["name"]

	conn := middleware.GetDBConn(req)
	ctx := req.Context()

	tx, err := conn.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
	if err != nil {
		http.Error(
			rw, fmt.Sprintf("DB error: %v.", err),
			http.StatusInternalServerError)
		return
	}
	defer tx.Rollback()

	var (
		id           int64
		lower, upper string
		data         []byte
		objnam       string
		nobjnam      sql.NullString
		dateInfo     time.Time
		source       string
	)

	switch err := tx.QueryRowContext(ctx, selectStretchSQL, name).Scan(
		&id,
		&lower, &upper,
		&data,
		&objnam, &nobjnam,
		&dateInfo,
		&source,
	); {
	case err == sql.ErrNoRows:
		http.NotFound(rw, req)
		return
	case err != nil:
		http.Error(
			rw, fmt.Sprintf("DB error: %v.", err),
			http.StatusInternalServerError)
		return
	}

	var countries []string

	if err := func() error {
		rows, err := tx.Query(selectStretchCountriesSQL, id)
		if err != nil {
			return err
		}
		defer rows.Close()

		for rows.Next() {
			var country string
			if err := rows.Scan(&country); err != nil {
				return err
			}
			countries = append(countries, country)
		}

		return rows.Err()
	}(); err != nil {
		http.Error(
			rw, fmt.Sprintf("DB error: %v.", err),
			http.StatusInternalServerError)
		return
	}

	var geom wkb.MultiPolygonGeom

	if err := geom.FromWKB(data); err != nil {
		http.Error(
			rw, fmt.Sprintf("Decoding WKB error: %v.", err),
			http.StatusInternalServerError)
		return
	}

	tmp := config.TmpDir()

	dir, err := ioutil.TempDir(tmp, "stretch-download")
	if err != nil {
		http.Error(
			rw, fmt.Sprintf("Cannot create temp dir: %v.", err),
			http.StatusInternalServerError)
		return
	}
	defer os.RemoveAll(dir)

	shpDir := filepath.Join(dir, "stretch")

	if err := os.Mkdir(shpDir, os.ModePerm); err != nil {
		http.Error(
			rw, fmt.Sprintf("Cannot create temp dir: %v.", err),
			http.StatusInternalServerError)
		return
	}

	filename := filepath.Join(shpDir, "stretch")

	if err := func() error {
		writer, err := shp.Create(filename, shp.POLYGON)
		if err != nil {
			return err
		}
		defer writer.Close()

		// TODO: Write geometry
		p := asShpPolygon(geom)

		writer.Write(p)

		return nil
	}(); err != nil {
		http.Error(
			rw, fmt.Sprintf("creating shapefile failed: %v.", err),
			http.StatusInternalServerError)
		return
	}

	entries, err := func() ([]os.FileInfo, error) {
		f, err := os.Open(shpDir)
		if err != nil {
			return nil, err
		}
		defer f.Close()
		return f.Readdir(-1)
	}()
	if err != nil {
		http.Error(
			rw, fmt.Sprintf("cannot read directory: %v.", err),
			http.StatusInternalServerError)
		return
	}

	rw.Header().Set("Content-Type", "application/zip")
	rw.Header().Set("Content-Disposition", `attachment; filename="stretch.zip"`)

	zipfile := zip.NewWriter(rw)
	defer zipfile.Close()

	now := time.Now()

	base := filepath.Base(shpDir)
	for _, info := range entries {
		if !info.Mode().IsRegular() {
			continue
		}
		if err := func() error {
			srcFile, err := os.Open(filepath.Join(shpDir, info.Name()))
			if err != nil {
				return err
			}
			defer srcFile.Close()
			header := &zip.FileHeader{
				Name:     base + "/" + info.Name(),
				Method:   zip.Deflate,
				Modified: now,
			}
			out, err := zipfile.CreateHeader(header)
			if err != nil {
				return err
			}
			_, err = io.Copy(out, srcFile)
			return err
		}(); err != nil {
			log.Printf("error: cannot write file: %v\n", err)
			return
		}
	}
}