Skip to content

Commit 05799a5

Browse files
authored
Merge pull request #29 from THD-Spatial/27-bugfix-docker-setup
Fix Docker Setup & Improve Error Handling
2 parents 870d615 + a440b3a commit 05799a5

10 files changed

Lines changed: 154 additions & 147 deletions

File tree

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ City2TABULA focuses on scalable, database-centric processing of large national-
1616

1717
## Getting started
1818

19-
- Setup & installation guide (Docker + manual): [docs/getting-started/setup.md](docs/installation/setup.md)
20-
- Full documentation: [city2tabula docs](https://thd-spatial.github.io/city2tabula)
19+
For setup and installation guide, please refer to [docs/getting-started/setup.md](https://thd-spatial.github.io/city2tabula/installation/setup/)
2120

2221
Local docs preview (MkDocs):
2322

cmd/main.go

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"os"
77
"strings"
8-
"sync"
98
"time"
109

1110
"github.com/THD-Spatial/City2TABULA/internal/config"
@@ -63,6 +62,14 @@ func main() {
6362
if *createDB {
6463
utils.Info.Println("Creating complete City2TABULA database...")
6564
if err := db.CreateCompleteDatabase(&config, pool); err != nil {
65+
if strings.Contains(err.Error(), "already exists") {
66+
utils.Error.Println("Failed to create database:", err)
67+
utils.Info.Println("")
68+
utils.Info.Println("The database appears to have been set up already.")
69+
utils.Info.Println("To start fresh, run: ./city2tabula --reset-all (or 'make reset-db')")
70+
utils.Info.Println("To re-extract only: ./city2tabula -extract-features (or 'make extract-features')")
71+
os.Exit(1)
72+
}
6673
utils.Error.Fatalf("Failed to create database: %v", err)
6774
}
6875
utils.Info.Println("Database creation completed successfully")
@@ -132,8 +139,7 @@ func runFeatureExtraction(config *config.Config, pool *pgxpool.Pool) error {
132139
}
133140

134141
// Check if there are any buildings to process
135-
totalBuildings := len(lod2BldIDs) + len(lod3BldIDs)
136-
if totalBuildings == 0 {
142+
if len(lod2BldIDs)+len(lod3BldIDs) == 0 {
137143
utils.Warn.Println("No buildings found in either LOD2 or LOD3 schemas. Nothing to extract.")
138144
return nil
139145
}
@@ -158,29 +164,8 @@ func runFeatureExtraction(config *config.Config, pool *pgxpool.Pool) error {
158164
if pipQueue.Len() > 0 {
159165
utils.PrintPipelineQueueInfo(pipQueue.Len(), len(pipQueue.Peek().Jobs), config.Batch)
160166
} else {
161-
utils.Warn.Printf("Pipeline queue is empty - this shouldn't happen if buildings were found. Check batch creation logic.")
162-
// Continue anyway - workers will just have no work to do
163-
}
164-
165-
// Create pipeline channel
166-
pipChan := make(chan *process.Pipeline, pipQueue.Len())
167-
for !pipQueue.IsEmpty() {
168-
pipeline := pipQueue.Dequeue()
169-
if pipeline != nil {
170-
pipChan <- pipeline
171-
}
172-
}
173-
close(pipChan)
174-
175-
// Start workers
176-
numWorkers := config.Batch.Threads
177-
var wg sync.WaitGroup
178-
for i := 1; i <= numWorkers; i++ {
179-
wg.Add(1)
180-
worker := process.NewWorker(i)
181-
go worker.Start(pipChan, pool, &wg, config)
167+
utils.Warn.Printf("Pipeline queue is empty - this shouldn't happen if buildings were found.")
182168
}
183-
wg.Wait()
184169

185-
return nil
170+
return process.RunPipelineQueue(pipQueue, pool, config)
186171
}

environment/docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ services:
1313
extra_hosts:
1414
- "host.docker.internal:host-gateway" # Linux compatibility
1515
ports:
16-
- "8080:8080" # map port 8080 for application access
16+
- "${HOST_PORT}:5000" # map host port (configurable) to container port 8080
1717
command: bash # start an interactive shell for development

environment/docker.env

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ COUNTRY=germany
2828
# Note: These countries are available based on the TABULA and EPISCOPE project data. Therefore running pipeline on other countries is not supported.
2929

3030

31+
# Application port mapping (host port -> container 8080)
32+
# Change this if 8080 collides with another service
33+
HOST_PORT=5000
34+
3135
# Database connection settings (Connect to host PostgreSQL)
3236
DB_HOST=host.docker.internal
3337
DB_PORT=5432

internal/db/setup.go

Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"fmt"
66
"os"
7-
"sync"
87

98
"github.com/THD-Spatial/City2TABULA/internal/config"
109
"github.com/THD-Spatial/City2TABULA/internal/importer"
@@ -23,7 +22,6 @@ type SetupOperation struct {
2322

2423
// CreateCompleteDatabase creates the complete City2TABULA database with CityDB infrastructure
2524
func CreateCompleteDatabase(config *config.Config, conn *pgxpool.Pool) error {
26-
utils.Info.Println("Creating complete City2TABULA database...")
2725

2826
// Step 1: Create CityDB infrastructure
2927
if err := CreateCityDB(config); err != nil {
@@ -40,7 +38,6 @@ func CreateCompleteDatabase(config *config.Config, conn *pgxpool.Pool) error {
4038
return fmt.Errorf("failed to import data: %w", err)
4139
}
4240

43-
utils.Debug.Println("Complete database created successfully")
4441
return nil
4542
}
4643

@@ -99,23 +96,9 @@ func RunCity2TabulaDBSetup(config *config.Config, conn *pgxpool.Pool) error {
9996
return fmt.Errorf("failed to setup main DB queue: %w", err)
10097
}
10198

102-
mainPipelineChan := make(chan *process.Pipeline, mainPipelineQueue.Len())
103-
for !mainPipelineQueue.IsEmpty() {
104-
pipeline := mainPipelineQueue.Dequeue()
105-
if pipeline != nil {
106-
mainPipelineChan <- pipeline
107-
}
108-
}
109-
close(mainPipelineChan)
110-
111-
numWorkers := config.Batch.Threads
112-
var wg sync.WaitGroup
113-
for i := 1; i <= numWorkers; i++ {
114-
wg.Add(1)
115-
worker := process.NewWorker(i)
116-
go worker.Start(mainPipelineChan, conn, &wg, config)
99+
if err := process.RunPipelineQueue(mainPipelineQueue, conn, config); err != nil {
100+
return fmt.Errorf("main DB setup failed: %w", err)
117101
}
118-
wg.Wait()
119102

120103
utils.Info.Println("Main database setup completed")
121104

@@ -125,21 +108,9 @@ func RunCity2TabulaDBSetup(config *config.Config, conn *pgxpool.Pool) error {
125108
return fmt.Errorf("failed to setup supplementary DB queue: %w", err)
126109
}
127110

128-
supplementaryPipelineChan := make(chan *process.Pipeline, supplementaryPipelineQueue.Len())
129-
for !supplementaryPipelineQueue.IsEmpty() {
130-
pipeline := supplementaryPipelineQueue.Dequeue()
131-
if pipeline != nil {
132-
supplementaryPipelineChan <- pipeline
133-
}
134-
}
135-
close(supplementaryPipelineChan)
136-
137-
for i := 1; i <= numWorkers; i++ {
138-
wg.Add(1)
139-
worker := process.NewWorker(i)
140-
go worker.Start(supplementaryPipelineChan, conn, &wg, config)
111+
if err := process.RunPipelineQueue(supplementaryPipelineQueue, conn, config); err != nil {
112+
return fmt.Errorf("supplementary DB setup failed: %w", err)
141113
}
142-
wg.Wait()
143114

144115
utils.Info.Println("Supplementary database setup completed")
145116

internal/process/worker.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,26 @@ func (w *Worker) Start(pipelineChan <-chan *Pipeline, conn *pgxpool.Pool, wg *sy
2828
}
2929
}
3030
}
31+
32+
// RunPipelineQueue drains a PipelineQueue into a channel and runs it with workers.
33+
func RunPipelineQueue(queue *PipelineQueue, conn *pgxpool.Pool, cfg *config.Config) error {
34+
if queue.IsEmpty() {
35+
return nil
36+
}
37+
38+
pipChan := make(chan *Pipeline, queue.Len())
39+
for !queue.IsEmpty() {
40+
if p := queue.Dequeue(); p != nil {
41+
pipChan <- p
42+
}
43+
}
44+
close(pipChan)
45+
46+
var wg sync.WaitGroup
47+
for i := 1; i <= cfg.Batch.Threads; i++ {
48+
wg.Add(1)
49+
go NewWorker(i).Start(pipChan, conn, &wg, cfg)
50+
}
51+
wg.Wait()
52+
return nil
53+
}

internal/utils/citydb.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,5 +108,11 @@ func ExecuteCityDBScript(cfg *config.Config, sqlFilePath, schemaName string) err
108108
if len(out) > 0 {
109109
Debug.Printf("psql output:\n%s", string(out))
110110
}
111-
return err
111+
if err != nil {
112+
if strings.Contains(string(out), "already exists") {
113+
return fmt.Errorf("schema already exists (psql output: %s): %w", strings.TrimSpace(string(out)), err)
114+
}
115+
return fmt.Errorf("failed to execute CityDB script %s: %w", sqlFilePath, err)
116+
}
117+
return nil
112118
}

makefile

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,45 +7,49 @@ help: ## Show this help message
77
@echo "City2TABULA Make Commands"
88
@echo "========================"
99
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
10+
@echo ""
11+
@echo "Recommended Workflow:"
12+
@echo " 1. make configure - Set country, DB credentials in environment/docker.env"
13+
@echo " 2. make build - Build the Docker image"
14+
@echo " 3. make create-db - Create database schemas and import CityDB data"
15+
@echo " (If database already exists, run: make reset-db)"
16+
@echo " 4. make extract-features - Run the feature extraction pipeline"
17+
@echo ""
1018

1119
##@ Docker Environment
1220
build: ## Build the Docker environment
13-
cd environment && docker compose build --no-cache
21+
cd environment && docker compose --env-file docker.env build --no-cache
1422

1523
up: ## Start the Docker environment
16-
cd environment && docker compose up -d
24+
cd environment && docker compose --env-file docker.env up -d
1725

1826
down: ## Stop the Docker environment
19-
cd environment && docker compose down
27+
cd environment && docker compose --env-file docker.env down
2028

2129
logs: ## View Docker logs
22-
cd environment && docker compose logs -f
30+
cd environment && docker compose --env-file docker.env logs -f
2331

2432
status: ## Check container status
25-
cd environment && docker compose ps
33+
cd environment && docker compose --env-file docker.env ps
2634

2735
##@ Application Commands
2836
dev: ## Start development environment with shell
29-
cd environment && docker compose up -d && docker exec -it city2tabula-environment bash
37+
cd environment && docker compose --env-file docker.env up -d && docker exec -it city2tabula-environment bash
3038

3139
create-db: up ## Create database and setup schemas
32-
cd environment && docker exec -it city2tabula-environment ./city2tabula -create_db
40+
cd environment && docker exec -it city2tabula-environment ./city2tabula -create-db
3341

3442
extract-features: up ## Extract building features
35-
cd environment && docker exec -it city2tabula-environment ./city2tabula -extract_features
43+
cd environment && docker exec -it city2tabula-environment ./city2tabula -extract-features
3644

3745
reset-db: up ## Reset the entire database
38-
cd environment && docker exec -it city2tabula-environment ./city2tabula -reset_all
46+
cd environment && docker exec -it city2tabula-environment ./city2tabula -reset-all
3947

4048
##@ Complete Workflows
4149
configure: ## Interactive configuration: select country and enter password
4250
@echo "City2TABULA Interactive Configuration"
4351
@echo "========================================"
4452
@echo ""
45-
@echo "Copying base environment configuration..."
46-
@cp environment/docker.env .env
47-
@echo "Base configuration copied!"
48-
@echo ""
4953
@echo "Available Countries:"
5054
@echo "======================"
5155
@echo " 1) austria - SRID: 31256 (MGI / Austria GK East)"
@@ -68,7 +72,7 @@ configure: ## Interactive configuration: select country and enter password
6872
@echo "18) sweden - SRID: 3006 (SWEREF99 TM)"
6973
@echo "19) united_kingdom - SRID: 27700 (OSGB 1936 / British National Grid)"
7074
@echo ""
71-
@read -p "Select country (1-19): " choice; \
75+
@read -p "For which country would you like to configure City2TABULA? Enter a number (1-19): " choice; \
7276
case $$choice in \
7377
1) COUNTRY="austria"; SRID="31256"; SRS_NAME="MGI / Austria GK East" ;; \
7478
2) COUNTRY="belgium"; SRID="31370"; SRS_NAME="Belgian Lambert 72" ;; \
@@ -96,28 +100,27 @@ configure: ## Interactive configuration: select country and enter password
96100
echo ""; \
97101
echo "Database Configuration:"; \
98102
echo "========================="; \
99-
echo -n "Enter PostgreSQL username [default: postgres]: "; \
100-
read pg_user; \
103+
read -p "Enter PostgreSQL username [default: postgres]: " pg_user; \
101104
if [ -z "$$pg_user" ]; then pg_user="postgres"; fi; \
102105
echo -n "Enter PostgreSQL password: "; \
103106
stty -echo; \
104107
read pg_password; \
105108
stty echo; \
106109
echo ""; \
107110
echo ""; \
108-
echo "Updating configuration file..."; \
111+
echo "Updating environment/docker.env..."; \
109112
if [ "$$(uname)" = "Darwin" ]; then \
110-
sed -i '' "s/^COUNTRY=.*/COUNTRY=$$COUNTRY/" .env; \
111-
sed -i '' "s/^CITYDB_SRID=.*/CITYDB_SRID=$$SRID/" .env; \
112-
sed -i '' "s|^CITYDB_SRS_NAME=.*|CITYDB_SRS_NAME=$$SRS_NAME|" .env; \
113-
sed -i '' "s/^DB_USER=.*/DB_USER=$$pg_user/" .env; \
114-
sed -i '' "s/<your_pg_password>/$$pg_password/" .env; \
113+
sed -i '' "s/^COUNTRY=.*/COUNTRY=$$COUNTRY/" environment/docker.env; \
114+
sed -i '' "s/^CITYDB_SRID=.*/CITYDB_SRID=$$SRID/" environment/docker.env; \
115+
sed -i '' "s|^CITYDB_SRS_NAME=.*|CITYDB_SRS_NAME=$$SRS_NAME|" environment/docker.env; \
116+
sed -i '' "s/^DB_USER=.*/DB_USER=$$pg_user/" environment/docker.env; \
117+
sed -i '' "s/^DB_PASSWORD=.*/DB_PASSWORD=$$pg_password/" environment/docker.env; \
115118
else \
116-
sed -i "s/^COUNTRY=.*/COUNTRY=$$COUNTRY/" .env; \
117-
sed -i "s/^CITYDB_SRID=.*/CITYDB_SRID=$$SRID/" .env; \
118-
sed -i "s|^CITYDB_SRS_NAME=.*|CITYDB_SRS_NAME=$$SRS_NAME|" .env; \
119-
sed -i "s/^DB_USER=.*/DB_USER=$$pg_user/" .env; \
120-
sed -i "s/<your_pg_password>/$$pg_password/" .env; \
119+
sed -i "s/^COUNTRY=.*/COUNTRY=$$COUNTRY/" environment/docker.env; \
120+
sed -i "s/^CITYDB_SRID=.*/CITYDB_SRID=$$SRID/" environment/docker.env; \
121+
sed -i "s|^CITYDB_SRS_NAME=.*|CITYDB_SRS_NAME=$$SRS_NAME|" environment/docker.env; \
122+
sed -i "s/^DB_USER=.*/DB_USER=$$pg_user/" environment/docker.env; \
123+
sed -i "s/^DB_PASSWORD=.*/DB_PASSWORD=$$pg_password/" environment/docker.env; \
121124
fi; \
122125
echo "Configuration completed!"; \
123126
echo ""; \
@@ -129,22 +132,21 @@ configure: ## Interactive configuration: select country and enter password
129132
echo "Database: Configured"; \
130133
echo ""; \
131134
echo "Next steps:"; \
132-
echo "- Place your data in data/lod2/$$COUNTRY/ and data/lod3/$$COUNTRY/"
135+
echo "- Place your data in data/lod2/$$COUNTRY/ and data/lod3/$$COUNTRY/"; \
133136
echo "- Run 'make up' to start containers"; \
134-
echo "- Run 'make dev' to access development shell"; \
137+
echo "- Run 'make dev' to access development shell"
135138

136139

137-
setup: build configure ## Build environment, copy .env, and start containers
140+
setup: build configure ## Build environment, configure, and start containers
138141
@$(MAKE) up
139142
@echo "Environment is ready! Run 'make dev' to access the shell"
140-
@echo "Don't forget to edit .env with your PostgreSQL password if you haven't already"
141143

142144
quick-start: setup create-db extract-features ## Complete setup and processing
143145
@echo "Quick start complete!"
144146

145147
##@ Cleanup
146148
clean: ## Stop containers and remove volumes
147-
cd environment && docker compose down -v
149+
cd environment && docker compose --env-file docker.env down -v
148150

149151
clean-all: ## Remove containers, volumes, and images
150-
cd environment && docker compose down -v --rmi all
152+
cd environment && docker compose --env-file docker.env down -v --rmi all

0 commit comments

Comments
 (0)