Skip to content

Commit

Permalink
osm-zoning displays road geometry
Browse files Browse the repository at this point in the history
  • Loading branch information
baditaflorin committed Jun 14, 2024
1 parent 431cdf4 commit bde70b2
Show file tree
Hide file tree
Showing 15 changed files with 649 additions and 9 deletions.
29 changes: 20 additions & 9 deletions .idea/workspace.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions osm-zoning/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package config

import (
"log"
"os"

"github.com/joho/godotenv"
)

type Config struct {
ClientID string
ClientSecret string
RedirectURI string
AuthURL string
TokenURL string
Query string
ChangesetComment string
CreatedBy string
}

func LoadConfig() *Config {
err := godotenv.Load()
if err != nil {
log.Fatalf("Error loading .env file: %v", err)
}

return &Config{
ClientID: os.Getenv("CLIENT_ID"),
ClientSecret: os.Getenv("CLIENT_SECRET"),
RedirectURI: os.Getenv("REDIRECT_URI"),
AuthURL: os.Getenv("AUTH_URL"),
TokenURL: os.Getenv("TOKEN_URL"),
Query: os.Getenv("QUERY"),
ChangesetComment: os.Getenv("CHANGESET_COMMENT"),
CreatedBy: os.Getenv("CREATED_BY"),
}
}
6 changes: 6 additions & 0 deletions osm-zoning/constants/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package constants

const (
ChangesetComment = "Updated road information"
CreatedBy = "OSM-ZoningApp"
)
104 changes: 104 additions & 0 deletions osm-zoning/handlers/handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package handlers

import (
"encoding/json"
"fmt"
"log"
"net/http"
"osm-zoning/config"
"osm-zoning/oauth"
"osm-zoning/osm"

"golang.org/x/oauth2"
)

func HandleData(cfg *config.Config) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
bbox := r.URL.Query().Get("bbox")
if bbox == "" {
http.Error(w, "Bounding box is required", http.StatusBadRequest)
return
}

// Fetch ways within the given bounding box
osm.FetchWays(cfg, bbox)

w.Header().Set("Content-Type", "application/json")
data, err := json.Marshal(osm.Ways.Elements)
if err != nil {
http.Error(w, "Failed to encode data: "+err.Error(), http.StatusInternalServerError)
return
}

// Log the data that will be sent
log.Printf("Serving data: %s", data)

w.Write(data)
}
}

func HandleMap(cfg *config.Config) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "static/map.html")
}
}

func HandleLogin(cfg *config.Config) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
url := oauth.Oauth2Config.AuthCodeURL("state", oauth2.AccessTypeOffline)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}
}

func HandleCallback(cfg *config.Config) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
token, err := oauth.Oauth2Config.Exchange(oauth2.NoContext, code)
if err != nil {
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
return
}

session, _ := oauth.Store.Get(r, "session-name")
session.Values["oauth-token"] = token
err = session.Save(r, w)
if err != nil {
http.Error(w, "Failed to save session: "+err.Error(), http.StatusInternalServerError)
return
}
log.Printf("OAuth token saved in session: %v", token)

http.Redirect(w, r, "/", http.StatusSeeOther)
}
}

func HandleAddWay(cfg *config.Config) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
return
}

var way struct {
Nodes []int64 `json:"nodes"`
Tags map[string]string `json:"tags"`
}
if err := json.NewDecoder(r.Body).Decode(&way); err != nil {
http.Error(w, "Failed to parse request body: "+err.Error(), http.StatusBadRequest)
return
}

session, _ := oauth.Store.Get(r, "session-name")
token, ok := session.Values["oauth-token"].(*oauth2.Token)
if !ok {
http.Error(w, "You are not authenticated. Please log in to add a new road.", http.StatusUnauthorized)
log.Println("No OAuth token found in session")
return
}
log.Printf("Retrieved OAuth token from session: %v", token)

oauth.CreateMapWay(cfg, token, way.Nodes, way.Tags)

fmt.Fprintf(w, "Way created successfully")
}
}
31 changes: 31 additions & 0 deletions osm-zoning/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package main

import (
"fmt"
"log"
"net/http"
"osm-zoning/config"
"osm-zoning/handlers"
"osm-zoning/oauth"
)

func main() {
cfg := config.LoadConfig()

oauth.Init(cfg)

fs := http.FileServer(http.Dir("static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))

http.HandleFunc("/", handlers.HandleMap(cfg))
http.HandleFunc("/data", handlers.HandleData(cfg))
http.HandleFunc("/login", handlers.HandleLogin(cfg))
http.HandleFunc("/callback", handlers.HandleCallback(cfg))
http.HandleFunc("/addway", handlers.HandleAddWay(cfg))

fmt.Println("Server starting on :7777...")
err := http.ListenAndServe(":7777", nil)
if err != nil {
log.Fatalf("Error starting server: %s", err)
}
}
129 changes: 129 additions & 0 deletions osm-zoning/oauth/oauth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package oauth

import (
"encoding/gob"
"fmt"
"log"
"net/http"
"osm-zoning/config"
"osm-zoning/utils"

"github.com/gorilla/sessions"
"golang.org/x/oauth2"
)

var (
Oauth2Config *oauth2.Config
Store = sessions.NewCookieStore([]byte("super-secret-key"))
)

func Init(cfg *config.Config) {
gob.Register(&oauth2.Token{})

Oauth2Config = &oauth2.Config{
ClientID: cfg.ClientID,
ClientSecret: cfg.ClientSecret,
RedirectURL: cfg.RedirectURI,
Scopes: []string{
"read_prefs",
"write_prefs",
"write_api",
},
Endpoint: oauth2.Endpoint{
AuthURL: cfg.AuthURL,
TokenURL: cfg.TokenURL,
},
}
}

func createChangesetRequest(cfg *config.Config, token *oauth2.Token) (*http.Request, error) {
xmlData := fmt.Sprintf(`
<osm>
<changeset>
<tag k="created_by" v="%s"/>
<tag k="comment" v="%s"/>
</changeset>
</osm>`, cfg.CreatedBy, cfg.ChangesetComment)

return utils.CreateRequest("PUT", "https://api.openstreetmap.org/api/0.6/changeset/create", "text/xml", []byte(xmlData))
}

func createWayRequest(changesetID int, nodes []int64, tags map[string]string) (*http.Request, error) {
var tagsXML string
for key, value := range tags {
tagsXML += fmt.Sprintf(`<tag k="%s" v="%s"/>`, key, value)
}

var nodesXML string
for _, node := range nodes {
nodesXML += fmt.Sprintf(`<nd ref="%d"/>`, node)
}

xmlData := fmt.Sprintf(`
<osm>
<way changeset="%d">
%s
%s
</way>
</osm>`, changesetID, nodesXML, tagsXML)

return utils.CreateRequest("PUT", "https://api.openstreetmap.org/api/0.6/way/create", "text/xml", []byte(xmlData))
}

func CreateChangeset(cfg *config.Config, token *oauth2.Token) (int, error) {
client := Oauth2Config.Client(oauth2.NoContext, token)
req, err := createChangesetRequest(cfg, token)
if err != nil {
return 0, err
}
req.Header.Set("Authorization", "Bearer "+token.AccessToken)

body, err := utils.DoRequest(client, req)
if err != nil {
return 0, err
}

var changesetID int
fmt.Sscanf(string(body), "%d", &changesetID)

return changesetID, nil
}

func CloseChangeset(token *oauth2.Token, changesetID int) error {
client := Oauth2Config.Client(oauth2.NoContext, token)
endpointURL := fmt.Sprintf("https://api.openstreetmap.org/api/0.6/changeset/%d/close", changesetID)

req, err := utils.CreateRequest("PUT", endpointURL, "text/xml", nil)
if err != nil {
return fmt.Errorf("failed to create request: %v", err)
}
req.Header.Set("Authorization", "Bearer "+token.AccessToken)

_, err = utils.DoRequest(client, req)
return err
}

func CreateMapWay(cfg *config.Config, token *oauth2.Token, nodes []int64, tags map[string]string) {
changesetID, err := CreateChangeset(cfg, token)
if err != nil {
log.Fatalf("Failed to create changeset: %v", err)
}

client := Oauth2Config.Client(oauth2.NoContext, token)
req, err := createWayRequest(changesetID, nodes, tags)
if err != nil {
log.Fatalf("Failed to create request: %v", err)
}
req.Header.Set("Authorization", "Bearer "+token.AccessToken)

_, err = utils.DoRequest(client, req)
if err != nil {
log.Fatalf("Failed to create way: %v", err)
}

fmt.Printf("Way created successfully\n")

if err := CloseChangeset(token, changesetID); err != nil {
log.Fatalf("Failed to close changeset: %v", err)
}
}
16 changes: 16 additions & 0 deletions osm-zoning/osm.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[Unit]
Description=OSM Zoning Map
After=network.target
StartLimitIntervalSec=0

[Service]
Type=simple
Restart=always
RestartSec=1
User=root
RuntimeMaxSec=1d
WorkingDirectory=/opt/osm-zoning
ExecStart=/opt/osm-zoning/backend

[Install]
WantedBy=multi-user.target
Loading

0 comments on commit bde70b2

Please sign in to comment.