Add proxy for managing tiles
This commit is contained in:
parent
d6407933f8
commit
3743d63692
10 changed files with 316 additions and 198 deletions
|
|
@ -24,7 +24,7 @@ func AddRoutes(r chi.Router) {
|
|||
r.Method("POST", "/image/{uuid}/content", auth.NewEnsureAuth(apiImageContentPost))
|
||||
r.Method("GET", "/leads", authenticatedHandlerJSON(listLead))
|
||||
r.Method("POST", "/leads", authenticatedHandlerJSONPost(postLeads))
|
||||
r.Method("GET", "/tile//{z}/{y}/{x}", auth.NewEnsureAuth(getTile))
|
||||
r.Method("GET", "/tile/{z}/{y}/{x}", auth.NewEnsureAuth(getTile))
|
||||
|
||||
// Unauthenticated endpoints
|
||||
r.Get("/district", apiGetDistrict)
|
||||
|
|
|
|||
67
api/tile.go
67
api/tile.go
|
|
@ -1,12 +1,21 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform/imagetile"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func getTile(w http.ResponseWriter, r *http.Request, org *models.Organization, user *models.User) {
|
||||
|
|
@ -29,5 +38,61 @@ func getTile(w http.ResponseWriter, r *http.Request, org *models.Organization, u
|
|||
http.Error(w, "can't parse x as an integer", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "%d, %d, %d", x, y, z)
|
||||
err = handleTile(r.Context(), w, org, uint(z), uint(y), uint(x))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to do tile")
|
||||
http.Error(w, "failed to do tile", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
func handleTile(ctx context.Context, w http.ResponseWriter, org *models.Organization, z, y, x uint) error {
|
||||
if org.ArcgisMapServiceID.IsNull() {
|
||||
return fmt.Errorf("no map service ID set")
|
||||
}
|
||||
map_service_id := org.ArcgisMapServiceID.MustGet()
|
||||
tile_path := fmt.Sprintf("%s/tile-cache/%s/%d/%d/%d.raw", config.FilesDirectory, map_service_id, z, y, x)
|
||||
file, err := os.Open(tile_path)
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
img, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("readall from %s: %w", tile_path, err)
|
||||
}
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(img)))
|
||||
_, err = io.Copy(w, bytes.NewBuffer(img))
|
||||
if err != nil {
|
||||
return fmt.Errorf("copy bytes from %s: %w", tile_path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
content, err := imagetile.ImageAtTile(ctx, org, uint(z), uint(y), uint(x))
|
||||
if err != nil {
|
||||
if errors.Is(err, imagetile.ErrNoTile) {
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(content)))
|
||||
_, err = io.Copy(w, bytes.NewBuffer(content))
|
||||
if err != nil {
|
||||
return fmt.Errorf("write image file: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("image at tile: %w", err)
|
||||
}
|
||||
parent := filepath.Dir(tile_path)
|
||||
err = os.MkdirAll(parent, 0750)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mkdirall: %w", err)
|
||||
}
|
||||
err = os.WriteFile(tile_path, content, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write image file: %w", err)
|
||||
}
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(content)))
|
||||
_, err = io.Copy(w, bytes.NewBuffer(content))
|
||||
if err != nil {
|
||||
return fmt.Errorf("write image file: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
153
html/static/js/map-proxied-arcgis-tile.js
Normal file
153
html/static/js/map-proxied-arcgis-tile.js
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
// A map that shows multiple single point locations.
|
||||
// Points have additional detail popups.
|
||||
// The background layer is proxied from Arcgis
|
||||
class MapProxiedArcgisTile extends HTMLElement {
|
||||
static observedAttributes = ["latitude", "longitude"];
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// Create a shadow DOM
|
||||
this.attachShadow({ mode: "open" });
|
||||
|
||||
// Initial render
|
||||
this.render();
|
||||
|
||||
// Keep track of any 'on' calls to add to the map as soon as we create it.
|
||||
this._preOns = [];
|
||||
this._map = null;
|
||||
this._markers = [];
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, old_value, new_value) {
|
||||
//console.log("map-arcgis-tile: attribute changed", name, old_value, new_value);
|
||||
if ((name == "latitude" || name == "longitude") && this._map != null) {
|
||||
const latitude = parseFloat(this.getAttribute("latitude"));
|
||||
const longitude = parseFloat(this.getAttribute("longitude"));
|
||||
this._map.jumpTo({
|
||||
center: [longitude, latitude],
|
||||
zoom: 19,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Lifecycle: when element is added to the DOM
|
||||
connectedCallback() {
|
||||
// Initialize the map when the element is added to the DOM
|
||||
setTimeout(() => this._initializeMap(), 0);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
if (this._map) {
|
||||
this._map.remove();
|
||||
}
|
||||
}
|
||||
|
||||
_initializeMap() {
|
||||
const latitude = parseFloat(this.getAttribute("latitude"));
|
||||
const longitude = parseFloat(this.getAttribute("longitude"));
|
||||
const organization_id = Number(this.getAttribute("organization-id") || 0);
|
||||
const tegola = this.getAttribute("tegola");
|
||||
const url_tiles = this.getAttribute("url-tiles");
|
||||
|
||||
const mapElement = this.shadowRoot.querySelector("#map");
|
||||
this._map = new maplibregl.Map({
|
||||
center: [longitude, latitude],
|
||||
container: mapElement,
|
||||
style: "https://tiles.stadiamaps.com/styles/osm_bright.json",
|
||||
zoom: 19,
|
||||
});
|
||||
this._map.on("load", () => {
|
||||
if (organization_id != 0) {
|
||||
this._map.addSource("tegola", {
|
||||
type: "vector",
|
||||
tiles: [
|
||||
`${tegola}maps/nidus/{z}/{x}/{y}?id=${organization_id}&organization_id=${organization_id}`,
|
||||
],
|
||||
});
|
||||
this._map.addLayer({
|
||||
id: "service-area",
|
||||
source: "tegola",
|
||||
"source-layer": "service-area-bounds",
|
||||
type: "line",
|
||||
paint: {
|
||||
"line-color": "#f00",
|
||||
},
|
||||
});
|
||||
}
|
||||
this._map.addSource("flyover", {
|
||||
type: "raster",
|
||||
tiles: [url_tiles],
|
||||
});
|
||||
this._map.addLayer({
|
||||
id: "flyover-layer",
|
||||
source: "flyover",
|
||||
type: "raster",
|
||||
});
|
||||
this.dispatchEvent(new CustomEvent("load"), {
|
||||
bubbles: true,
|
||||
composed: true, // Allows event to cross shadow DOM boundary
|
||||
detail: {
|
||||
map: this,
|
||||
},
|
||||
});
|
||||
});
|
||||
for (const on of this._preOns) {
|
||||
this._map.on(on.a, on.b);
|
||||
}
|
||||
}
|
||||
|
||||
// Initial render of component
|
||||
render() {
|
||||
this.shadowRoot.innerHTML = `
|
||||
<style>
|
||||
@import url("//unpkg.com/maplibre-gl@5.0.1/dist/maplibre-gl.css");
|
||||
#map {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="map"></div>
|
||||
`;
|
||||
}
|
||||
|
||||
addLayer(a) {
|
||||
return this._map.addLayer(a);
|
||||
}
|
||||
addSource(a, b) {
|
||||
return this._map.addSource(a, b);
|
||||
}
|
||||
jumpTo(args) {
|
||||
return this._map.jumpTo(args);
|
||||
}
|
||||
on(a, b) {
|
||||
if (this._map != null) {
|
||||
return this._map.on(a, b);
|
||||
} else {
|
||||
this._preOns.push({ a: a, b: b });
|
||||
}
|
||||
}
|
||||
once(a, b) {
|
||||
return this._map.once(a, b);
|
||||
}
|
||||
queryRenderedFeatures(a) {
|
||||
return this._map.queryRenderedFeatures(a);
|
||||
}
|
||||
|
||||
FitBounds(bounds, options) {
|
||||
return this._map.fitBounds(bounds, options);
|
||||
}
|
||||
SetLayoutProperty(layout, property, value) {
|
||||
return this._map.setLayoutProperty(layout, property, value);
|
||||
}
|
||||
SetMarkers(markers) {
|
||||
console.log("Setting map markers", markers);
|
||||
this._markers.forEach((marker) => marker.remove());
|
||||
this._markers = markers;
|
||||
for (let m of markers) {
|
||||
m.addTo(this._map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("map-proxied-arcgis-tile", MapProxiedArcgisTile);
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
var map = null;
|
||||
// A map that just shows a bunch of markers, it can't change them
|
||||
class MapWithMarkers extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// Create a shadow DOM
|
||||
this.attachShadow({mode: "open" });
|
||||
|
||||
// Initial render
|
||||
this.render();
|
||||
|
||||
this._map = null;
|
||||
|
||||
// markers shown on the map. Should be none or 1, generally.
|
||||
this._markers = [];
|
||||
}
|
||||
|
||||
// Lifecycle: when element is added to the DOM
|
||||
connectedCallback() {
|
||||
// Initialize the map when the element is added to the DOM
|
||||
setTimeout(() => this._initializeMap(), 0);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
if (this._map) {
|
||||
this._map.remove();
|
||||
}
|
||||
}
|
||||
|
||||
_initializeMap() {
|
||||
console.log("Setting up the map...");
|
||||
const apiKey = this.getAttribute("api-key");
|
||||
const lat = Number(this.getAttribute('latitude') || 36.2);
|
||||
const lng = Number(this.getAttribute('longitude') || -119.2);
|
||||
const zoom = Number(this.getAttribute('zoom') || 15);
|
||||
|
||||
mapboxgl.accessToken = apiKey;
|
||||
const mapElement = this.shadowRoot.querySelector("#map");
|
||||
this._map = new mapboxgl.Map({
|
||||
container: mapElement,
|
||||
center: {
|
||||
lat: lat,
|
||||
lng: lng,
|
||||
},
|
||||
style: 'mapbox://styles/mapbox/streets-v12', // style URL
|
||||
zoom: zoom,
|
||||
});
|
||||
this._map.on("load", () => {
|
||||
console.log("map loaded");
|
||||
this.dispatchEvent(new CustomEvent('load'), {
|
||||
bubbles: true,
|
||||
composed: true, // Allows event to cross shadow DOM boundary
|
||||
detail: {
|
||||
map: this
|
||||
}
|
||||
});
|
||||
});
|
||||
this._markers = [];
|
||||
}
|
||||
|
||||
// Initial render of component
|
||||
render() {
|
||||
this.shadowRoot.innerHTML = `
|
||||
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.0/mapbox-gl.css' rel='stylesheet' />
|
||||
<style>
|
||||
.map-container {
|
||||
background-color: #e9ecef;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
||||
height: 500px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
#map {
|
||||
height: 500px;
|
||||
width:100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
#map img {
|
||||
max-width: none;
|
||||
min-width: 0px;
|
||||
height: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="map-container" class="map-container">
|
||||
<div id="map"></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
jumpTo(args) {
|
||||
this._map.jumpTo(args);
|
||||
}
|
||||
|
||||
clearMarkers() {
|
||||
this._markers.forEach((marker) => marker.remove());
|
||||
}
|
||||
addMarker(coords, color) {
|
||||
console.log("Add marker", coords, color);
|
||||
const el = document.createElement("div");
|
||||
el.id = "marker";
|
||||
const marker = new mapboxgl.Marker({
|
||||
color: color,
|
||||
scale: 1.5,
|
||||
}).setLngLat(coords).addTo(this._map);
|
||||
this._markers.push(marker);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('map-with-markers', MapWithMarkers);
|
||||
|
|
@ -10,8 +10,7 @@
|
|||
defer
|
||||
src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"
|
||||
></script>
|
||||
<script src="https://unpkg.com/@esri/maplibre-arcgis@1.1.0/dist/umd/maplibre-arcgis.min.js"></script>
|
||||
<script src="/static/js/map-arcgis-tile.js"></script>
|
||||
<script src="/static/js/map-proxied-arcgis-tile.js"></script>
|
||||
<script src="/static/js/map-multipoint.js"></script>
|
||||
<script src="/static/js/time-relative.js"></script>
|
||||
<script>
|
||||
|
|
@ -38,9 +37,13 @@
|
|||
// Form fields for the selected task
|
||||
form: {
|
||||
poolCondition: "",
|
||||
poolShape: "",
|
||||
poolLocation: {
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
},
|
||||
ownerContact: "",
|
||||
residentContact: "",
|
||||
poolShape: "",
|
||||
},
|
||||
|
||||
// Computed: track which fields have changed
|
||||
|
|
@ -123,6 +126,8 @@
|
|||
|
||||
// Populate form with task values
|
||||
this.form = {
|
||||
latitude: task.location.latitude,
|
||||
longitude: task.location.longitude,
|
||||
poolCondition: task.condition || "",
|
||||
ownerContact: task.ownerContact || "",
|
||||
residentContact: task.residentContact || "",
|
||||
|
|
@ -274,7 +279,7 @@
|
|||
longitude: event.detail.lng,
|
||||
};
|
||||
map.SetMarkers([loc]);
|
||||
this.poolLocations[signal_id] = loc;
|
||||
this.form.poolLocation[signal_id] = loc;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -427,6 +432,30 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-3 col-form-label fw-bold"
|
||||
>Longitude:</label
|
||||
>
|
||||
<div class="col-sm-9">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
x-model="form.longitude"
|
||||
:class="{ 'border-warning': form.longitude !== originalValues.longitude }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-3 col-form-label fw-bold">Latitude:</label>
|
||||
<div class="col-sm-9">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
x-model="form.latitude"
|
||||
:class="{ 'border-warning': form.latitude !== originalValues.latitude }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-3 col-form-label fw-bold"
|
||||
>Pool Condition:</label
|
||||
|
|
@ -497,16 +526,17 @@
|
|||
|
||||
<!-- Aerial Image Placeholder -->
|
||||
<div class="map-container">
|
||||
<map-arcgis-tile
|
||||
<map-proxied-arcgis-tile
|
||||
class="map"
|
||||
arcgis-access-token="{{ .C.ArcgisAccessToken }}"
|
||||
organization-id="{{ .Organization.ID }}"
|
||||
tegola="{{ .URL.Tegola }}"
|
||||
{{ .C.URLTiles }}
|
||||
@map-click="updatePoolLocation($event, selectedTask.id)"
|
||||
:latitude="selectedTask?.location.latitude ?? 0"
|
||||
:longitude="selectedTask?.location.longitude ?? 0"
|
||||
>
|
||||
</map-arcgis-tile>
|
||||
</map-proxied-arcgis-tile>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
BIN
platform/imagetile/empty-tile.png
Normal file
BIN
platform/imagetile/empty-tile.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
|
|
@ -2,15 +2,36 @@ package imagetile
|
|||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/Gleipnir-Technology/arcgis-go"
|
||||
"github.com/Gleipnir-Technology/arcgis-go/fieldseeker"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/background"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
//"github.com/rs/zerolog/log"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
//go:embed empty-tile.png
|
||||
var emptyTileFS embed.FS
|
||||
|
||||
var ErrNoTile = errors.New("used placeholder tile")
|
||||
|
||||
var clientByOrgID = make(map[int32]*fieldseeker.FieldSeeker, 0)
|
||||
|
||||
func ImageAtPoint(ctx context.Context, org *models.Organization, level uint, lat, lng float64) ([]byte, error) {
|
||||
fssync, err := getFieldseeker(ctx, org)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("create fssync: %w", err)
|
||||
}
|
||||
map_service, err := aerialImageService(ctx, fssync.Arcgis)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("no map service: %w", err)
|
||||
}
|
||||
return map_service.TileGPS(ctx, level, lat, lng)
|
||||
}
|
||||
func ImageAtTile(ctx context.Context, org *models.Organization, level, y, x uint) ([]byte, error) {
|
||||
oauth, err := background.GetOAuthForOrg(ctx, org)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("get oauth for org: %w", err)
|
||||
|
|
@ -26,7 +47,20 @@ func ImageAtPoint(ctx context.Context, org *models.Organization, level uint, lat
|
|||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("no map service: %w", err)
|
||||
}
|
||||
return map_service.TileGPS(ctx, level, lat, lng)
|
||||
data, e := map_service.Tile(ctx, level, y, x)
|
||||
if e != nil {
|
||||
log.Error().Err(e).Msg("error getting tile")
|
||||
return []byte{}, fmt.Errorf("tile: %w", e)
|
||||
}
|
||||
// No data at this location, so supply the empty tile placeholder
|
||||
if len(data) == 0 {
|
||||
empty, err := emptyTileFS.ReadFile("empty-tile.png")
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("read empty tile: %w", err)
|
||||
}
|
||||
return empty, ErrNoTile
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func aerialImageService(ctx context.Context, gis *arcgis.ArcGIS) (*arcgis.MapService, error) {
|
||||
|
|
@ -39,3 +73,19 @@ func aerialImageService(ctx context.Context, gis *arcgis.ArcGIS) (*arcgis.MapSer
|
|||
}
|
||||
return nil, fmt.Errorf("non found")
|
||||
}
|
||||
func getFieldseeker(ctx context.Context, org *models.Organization) (*fieldseeker.FieldSeeker, error) {
|
||||
fssync, ok := clientByOrgID[org.ID]
|
||||
if ok {
|
||||
return fssync, nil
|
||||
}
|
||||
oauth, err := background.GetOAuthForOrg(ctx, org)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get oauth for org: %w", err)
|
||||
}
|
||||
fssync, err = background.NewFieldSeeker(
|
||||
ctx,
|
||||
oauth,
|
||||
)
|
||||
clientByOrgID[org.ID] = fssync
|
||||
return fssync, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import (
|
|||
"github.com/Gleipnir-Technology/nidus-sync/db/sql"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/html"
|
||||
"github.com/go-chi/chi/v5"
|
||||
//"github.com/rs/zerolog/log"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/stephenafamo/scan"
|
||||
/*
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||
|
|
@ -314,79 +314,6 @@ func getStatusByID(w http.ResponseWriter, r *http.Request) {
|
|||
)
|
||||
}
|
||||
|
||||
/*
|
||||
func postQuick(w http.ResponseWriter, r *http.Request) {
|
||||
err := r.ParseMultipartForm(32 << 10) // 32 MB buffer
|
||||
if err != nil {
|
||||
respondError(w, "Failed to parse form", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
lat := r.FormValue("latitude")
|
||||
lng := r.FormValue("longitude")
|
||||
comments := r.FormValue("comments")
|
||||
//photos := r.FormValue("photos")
|
||||
|
||||
latitude, err := strconv.ParseFloat(lat, 64)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to create parse latitude", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
longitude, err := strconv.ParseFloat(lng, 64)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to create parse longitude", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
u, err := GenerateReportID()
|
||||
if err != nil {
|
||||
respondError(w, "Failed to create quick report public ID", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
c, err := h3utils.GetCell(longitude, latitude, 15)
|
||||
setter := models.PublicreportQuickSetter{
|
||||
Created: omit.From(time.Now()),
|
||||
Comments: omit.From(comments),
|
||||
//Location: omitnull.From(fmt.Sprintf("ST_GeometryFromText(Point(%s %s))", longitude, latitude)),
|
||||
H3cell: omitnull.From(c.String()),
|
||||
PublicID: omit.From(u),
|
||||
ReporterEmail: omit.From(""),
|
||||
ReporterPhone: omit.From(""),
|
||||
}
|
||||
quick, err := models.PublicreportQuicks.Insert(&setter).One(r.Context(), db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to create database record", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
_, err = psql.Update(
|
||||
um.Table("publicreport.quick"),
|
||||
um.SetCol("location").To(fmt.Sprintf("ST_GeometryFromText('Point(%f %f)')", longitude, latitude)),
|
||||
um.Where(psql.Quote("id").EQ(psql.Arg(quick.ID))),
|
||||
).Exec(r.Context(), db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to insert publicreport", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
log.Info().Float64("latitude", latitude).Float64("longitude", longitude).Msg("Got upload")
|
||||
photoSetters := make([]*models.PublicreportQuickPhotoSetter, 0)
|
||||
uploads, err := extractPhotoUploads(r)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to extract photo uploads", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
for _, u := range uploads {
|
||||
photoSetters = append(photoSetters, &models.PublicreportQuickPhotoSetter{
|
||||
Filename: omit.From(u.Filename),
|
||||
Size: omit.From(u.Size),
|
||||
UUID: omit.From(u.UUID),
|
||||
})
|
||||
}
|
||||
err = quick.InsertQuickPhotos(r.Context(), db.PGInstance.BobDB, photoSetters...)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to create photo records", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, fmt.Sprintf("/quick-submit-complete?report=%s", u), http.StatusFound)
|
||||
}
|
||||
*/
|
||||
func sanitizeReportID(r string) string {
|
||||
result := ""
|
||||
for _, char := range r {
|
||||
|
|
|
|||
|
|
@ -2,9 +2,12 @@ package sync
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/background"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/html"
|
||||
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
||||
|
|
@ -13,6 +16,7 @@ import (
|
|||
|
||||
type contentReviewPool struct {
|
||||
ArcgisAccessToken string
|
||||
URLTiles template.HTMLAttr
|
||||
}
|
||||
type contentReviewRoot struct{}
|
||||
|
||||
|
|
@ -30,6 +34,7 @@ func getReviewPool(ctx context.Context, r *http.Request, org *models.Organizatio
|
|||
}
|
||||
return html.NewResponse("sync/review/pool.html", contentReviewPool{
|
||||
ArcgisAccessToken: access_token,
|
||||
URLTiles: template.HTMLAttr(fmt.Sprintf(`url-tiles="%s"`, config.MakeURLNidus("/api/tile/{z}/{y}/{x}"))),
|
||||
}), nil
|
||||
}
|
||||
func getReviewRoot(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*html.Response[contentReviewRoot], *nhttp.ErrorWithStatus) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ func audioFileContentWrite(audioUUID uuid.UUID, body io.Reader) error {
|
|||
}
|
||||
|
||||
var collectionToExtension map[Collection]string = map[Collection]string{
|
||||
CollectionAudioNormalized: "ogg",
|
||||
CollectionAudioRaw: "raw",
|
||||
CollectionAudioTranscoded: "ogg",
|
||||
CollectionCSV: "csv",
|
||||
|
|
@ -24,6 +25,7 @@ var collectionToExtension map[Collection]string = map[Collection]string{
|
|||
CollectionImageRaw: "raw",
|
||||
}
|
||||
var collectionToSubdir map[Collection]string = map[Collection]string{
|
||||
CollectionAudioNormalized: "audio-normalized",
|
||||
CollectionAudioRaw: "audio-raw",
|
||||
CollectionAudioTranscoded: "audio-transcoded",
|
||||
CollectionCSV: "csv",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue