Skip to content

Commit

Permalink
feat: query layer
Browse files Browse the repository at this point in the history
  • Loading branch information
samdyra committed Aug 17, 2024
1 parent 2431cce commit 79a84a6
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 63 deletions.
53 changes: 50 additions & 3 deletions internal/api/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# GeoSpatial Article Management System API Documentation

This document outlines the API endpoints for the GeoSpatial Article Management System, which includes authentication, article management, spatial data handling, layer group management, and MVT (Mapbox Vector Tiles) functionality.
This document outlines the API endpoints for the GeoSpatial Article Management System, which includes authentication, article management, spatial data handling, layer management, layer group management, and MVT (Mapbox Vector Tiles) functionality.

## Table of Contents
1. [Authentication API](#authentication-api)
2. [Article API](#article-api)
3. [Spatial Data API](#spatial-data-api)
4. [Layer Group API](#layer-group-api)
5. [MVT API](#mvt-api)
4. [Layer API](#layer-api)
5. [Layer Group API](#layer-group-api)
6. [MVT API](#mvt-api)

## Authentication API

Expand Down Expand Up @@ -107,6 +108,52 @@ Delete a spatial data table.

**Example:** `DELETE /spatial-data/existing_table`

## Layer API

Manages layers for visualizing spatial data.

### GET /layers
Retrieve layers.

**Query Parameters:**
- `id`: Comma-separated list of layer IDs or "*" for all layers

**Examples:**
- `GET /layers?id=1,2,3` (retrieve specific layers)
- `GET /layers?id=*` (retrieve all layers)

### POST /layers
Create a new layer.

**Request Body:**
```json
{
"spatial_data_id": 1,
"layer_name": "New Layer",
"coordinate": [0, 0],
"color": "#FF5733"
}
```

### PUT /layers/:id
Update an existing layer.

**Example:** `PUT /layers/1`

**Request Body:**
```json
{
"layer_name": "Updated Layer Name",
"coordinate": [1, 1],
"color": "#33FF57"
}
```

### DELETE /layers/:id
Delete a specific layer.

**Example:** `DELETE /layers/1`

## Layer Group API

Manages layer groups for organizing spatial data.
Expand Down
35 changes: 34 additions & 1 deletion internal/api/layer/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package layer
import (
"net/http"
"strconv"
"strings"

"github.com/gin-gonic/gin"
"github.com/samdyra/go-geo/internal/utils/errors"
Expand Down Expand Up @@ -34,7 +35,39 @@ func (h *Handler) CreateLayer(c *gin.Context) {
}

func (h *Handler) GetFormattedLayers(c *gin.Context) {
layers, err := h.service.GetFormattedLayers()
idParam := c.Query("id")

if idParam == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Please provide 'id' parameter. Use 'id=*' to fetch all layers"})
return
}

var ids []int64
var err error

if idParam == "*" {
// Fetch all layers
layers, err := h.service.GetAllFormattedLayers()
if err != nil {
c.JSON(http.StatusInternalServerError, errors.NewAPIError(err))
return
}
c.JSON(http.StatusOK, layers)
return
}

// Parse provided IDs
idStrings := strings.Split(idParam, ",")
for _, idStr := range idStrings {
id, err := strconv.ParseInt(strings.TrimSpace(idStr), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, errors.NewAPIError(errors.ErrInvalidInput))
return
}
ids = append(ids, id)
}

layers, err := h.service.GetFormattedLayers(ids)
if err != nil {
switch err {
case errors.ErrNotFound:
Expand Down
136 changes: 78 additions & 58 deletions internal/api/layer/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,64 +31,6 @@ func (s *Service) CreateLayer(layer LayerCreate, userID int64) error {
return nil
}

func (s *Service) GetFormattedLayers() ([]FormattedLayer, error) {
query := `SELECT l.id, l.layer_name, l.coordinate, l.color, sd.table_name, sd.type
FROM layer l
JOIN spatial_data sd ON l.spatial_data_id = sd.id`

rows, err := s.db.Query(query)
if err != nil {
return nil, errors.ErrInternalServer
}
defer rows.Close()

var result []FormattedLayer
for rows.Next() {
var id int64
var layerName, color, tableName, dataType string
var coordinate []float64
err := rows.Scan(&id, &layerName, &coordinate, &color, &tableName, &dataType)
if err != nil {
return nil, errors.ErrInternalServer
}

layerType := utils.GetLayerType(dataType)
paint := utils.GetPaint(dataType, color)

layer := map[string]interface{}{
"id": tableName,
"source": map[string]interface{}{
"type": "vector",
"tiles": fmt.Sprintf("http://localhost:8080/mvt/%s/{z}/{x}/{y}", tableName),
},
"source-layer": tableName,
"type": layerType,
"paint": paint,
}

layerJSON, err := json.Marshal(layer)
if err != nil {
return nil, errors.ErrInternalServer
}

result = append(result, FormattedLayer{
ID: id,
LayerName: layerName,
Coordinate: coordinate,
Layer: layerJSON,
})
}

if err = rows.Err(); err != nil {
return nil, errors.ErrInternalServer
}

if len(result) == 0 {
return nil, errors.ErrNotFound
}

return result, nil
}

func (s *Service) UpdateLayer(id int64, update LayerUpdate, userID int64) error {
query := "UPDATE layer SET updated_at = $1, updated_by = $2"
Expand Down Expand Up @@ -158,4 +100,82 @@ func (s *Service) DeleteLayer(id int64) error {
}

return tx.Commit()
}

func (s *Service) GetAllFormattedLayers() ([]FormattedLayer, error) {
query := `SELECT l.id, l.layer_name, l.coordinate, l.color, sd.table_name, sd.type
FROM layer l
JOIN spatial_data sd ON l.spatial_data_id = sd.id`

return s.queryFormattedLayers(query)
}

func (s *Service) GetFormattedLayers(ids []int64) ([]FormattedLayer, error) {
query := `SELECT l.id, l.layer_name, l.coordinate, l.color, sd.table_name, sd.type
FROM layer l
JOIN spatial_data sd ON l.spatial_data_id = sd.id
WHERE l.id IN (?)`

query, args, err := sqlx.In(query, ids)
if err != nil {
return nil, errors.ErrInternalServer
}

query = s.db.Rebind(query)
return s.queryFormattedLayers(query, args...)
}

func (s *Service) queryFormattedLayers(query string, args ...interface{}) ([]FormattedLayer, error) {
rows, err := s.db.Query(query, args...)
if err != nil {
return nil, errors.ErrInternalServer
}
defer rows.Close()

var result []FormattedLayer
for rows.Next() {
var id int64
var layerName, color, tableName, dataType string
var coordinate []float64
err := rows.Scan(&id, &layerName, &coordinate, &color, &tableName, &dataType)
if err != nil {
return nil, errors.ErrInternalServer
}

layerType := utils.GetLayerType(dataType)
paint := utils.GetPaint(dataType, color)

layer := map[string]interface{}{
"id": tableName,
"source": map[string]interface{}{
"type": "vector",
"tiles": fmt.Sprintf("http://localhost:8080/mvt/%s/{z}/{x}/{y}", tableName),
},
"source-layer": tableName,
"type": layerType,
"paint": paint,
}

layerJSON, err := json.Marshal(layer)
if err != nil {
return nil, errors.ErrInternalServer
}

result = append(result, FormattedLayer{
ID: id,
LayerName: layerName,
Coordinate: coordinate,
Layer: layerJSON,
})
}

if err = rows.Err(); err != nil {
return nil, errors.ErrInternalServer
}

if len(result) == 0 {
return nil, errors.ErrNotFound
}

return result, nil
}
2 changes: 1 addition & 1 deletion migrations/03_create_layer_table.sql
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
CREATE TABLE IF NOT EXISTS layer (
id SERIAL PRIMARY KEY,
data_id INTEGER REFERENCES data(id),
spatial_data_id INTEGER REFERENCES data(id),
layer_name VARCHAR(100) NOT NULL,
coordinate POINT,
color VARCHAR(7),
Expand Down

0 comments on commit 79a84a6

Please sign in to comment.