Zum Inhalt

🏛️ Schweizer Museen-Navigator

Eine Python-Anwendung mit tkinter-GUI und Neo4j-Graphdatenbank zur Verwaltung und Exploration von Schweizer Museen.


📋 Voraussetzungen

System-Anforderungen

  • Python 3.8+ installiert
  • Neo4j Desktop installiert und läuft
  • Neo4j-Datenbank erstellt

🚀 Installation

Schritt 1: Neo4j Desktop einrichten

Wichtig

Neo4j Desktop muss laufen, bevor die App gestartet wird!

  1. Neo4j Desktop herunterladen: https://neo4j.com/download/
  2. Installieren und starten
  3. Neue Datenbank erstellen:
  4. Name: museum-db (oder beliebig)
  5. Passwort setzen (z.B. password)
  6. Datenbank starten (grüner Play-Button)

Schritt 2: Python-Dependencies installieren

pip install -r requirements.txt
pip install neo4j

Schritt 3: Passwort konfigurieren

Passwort anpassen

Öffne museum_app.py und ändere Zeile 20:

def connect_to_neo4j(self):
    uri = "bolt://localhost:7687"
    user = "neo4j"
    password = "IHR_NEO4J_PASSWORT"  # ← HIER ÄNDERN!

▶️ Anwendung starten

python museum_app.py

Erste Schritte

Workflow

  1. Datenbank initialisieren: Klicke auf "📥 Datenbank initialisieren"
  2. Importiert ~28 Museen, 15 Kantone, 6 Kategorien, 5 Personen
  3. Erstellt Beziehungen zwischen den Nodes

  4. Museen durchsuchen:

  5. Wähle Kanton und/oder Kategorie
  6. Klicke "🔍 Suchen"

  7. Details ansehen: Klicke auf ein Museum in der Liste

  8. Statistiken: Klicke "📊 Statistik" für Top 10 Kantone


📊 Graph-Modell (Neo4j Schema)

Node-Labels

CREATE (m:Museum {
    name: "Kunsthaus Zürich",
    city: "Zürich",
    canton: "ZH",
    type: "Kunstmuseum",
    founded: 1787,
    url: "https://www.kunsthaus.ch"
})

Properties:

Property Typ Beschreibung
name String Museumsname (unique)
city String Stadt
canton String Kanton-Kürzel
type String Museumstyp
founded Integer Gründungsjahr
url String Website-URL
CREATE (c:Canton {
    abbr: "ZH",
    name: "Zürich",
    region: "Deutschschweiz",
    population: 1553423
})

Properties:

Property Typ Beschreibung
abbr String Kürzel (unique)
name String Vollständiger Name
region String Sprachregion
population Integer Einwohnerzahl
CREATE (cat:Category {
    name: "Kunstmuseum",
    description: "Bildende Kunst, Gemälde, Skulpturen"
})

Properties:

Property Typ Beschreibung
name String Kategoriename (unique)
description String Beschreibung
CREATE (p:Person {
    name: "Alberto Giacometti",
    years: "1901-1966",
    profession: "Bildhauer"
})

Properties:

Property Typ Beschreibung
name String Personenname
years String Lebensjahre
profession String Beruf

Relationship-Typen

graph LR
    M[Museum] -->|LOCATED_IN| C[Canton]
    M -->|BELONGS_TO| Cat[Category]
    M -->|FEATURES| P[Person]
    M -->|NEAR| M2[Museum]

    style M fill:#3498db,color:#fff
    style C fill:#2ecc71,color:#fff
    style Cat fill:#e74c3c,color:#fff
    style P fill:#f39c12,color:#fff
    style M2 fill:#3498db,color:#fff
Relationship Von → Nach Properties Beschreibung
LOCATED_IN Museum → Canton - Museum befindet sich in Kanton
BELONGS_TO Museum → Category - Museum gehört zu Kategorie
FEATURES Museum → Person description Museum zeigt Werke der Person
NEAR Museum ↔ Museum same_canton Museen im selben Kanton

Beispiel-Cypher-Queries

Query-Beispiele

MATCH (m:Museum)-[:LOCATED_IN]->(c:Canton {abbr: "ZH"})
RETURN m.name, m.city, m.type
ORDER BY m.name
MATCH (c:Canton)<-[:LOCATED_IN]-(m:Museum)
RETURN c.name, COUNT(m) AS museum_count
ORDER BY museum_count DESC
LIMIT 10
MATCH (p:Person {name: "Paul Klee"})<-[:FEATURES]-(m:Museum)
RETURN m.name, m.city
MATCH (m1:Museum {name: "Kunsthaus Zürich"})-[:NEAR]-(m2:Museum)
RETURN m2.name, m2.city
LIMIT 5

🎯 Features

Implementiert ✅

  • Neo4j-Verbindung mit Driver
  • Datenimport (Museen, Kantone, Kategorien, Personen)
  • Filter nach Kanton und Kategorie
  • Detailansicht mit verknüpften Personen
  • Anzeige ähnlicher Museen im selben Kanton
  • Statistik: Top 10 Kantone nach Museumsanzahl
  • Responsives tkinter-GUI

Noch nicht implementiert ❌

  • Export-Funktion (CSV/JSON)
  • Graph-Visualisierung
  • Person-Detail-Ansicht
  • Shortest Path zwischen Museen

📸 Für deinen Screencast

🎤 Frage 1: Warum Graph-DB für diesen Anwendungsfall?

Antwort: 3 Hauptvorteile

1. Beziehungen sind First-Class Citizens

// Neo4j: Direkt traversierbar
MATCH (m:Museum)-[:FEATURES]->(p:Person {name: "Paul Klee"})
RETURN m.name
// MongoDB: Komplexe Joins nötig
db.museums.aggregate([
  { $lookup: { from: "persons", localField: "person_ids", ... } },
  { $match: { "persons.name": "Paul Klee" } }
])

2. Flexible Queries über mehrere Hops

  • "Welche Museen zeigen Künstler aus dem gleichen Kanton?"
  • Neo4j: 1 Query mit Pattern-Matching
  • MongoDB: Multiple Queries + Application-Level-Logik

3. Neue Beziehungen ohne Schema-Änderung

// Einfach neue Beziehung hinzufügen
CREATE (m1)-[:COOPERATES_WITH {since: 2024}]->(m2)

In MongoDB müsste die Collection-Struktur angepasst werden.

Performance-Vergleich
Operation Neo4j MongoDB
Beziehung traversieren O(1) O(log n) mit Index
3-Hop-Query Direkt 3 separate Queries
Neue Beziehung CREATE Array-Update + Reindex

🎤 Frage 2: Wie Neo4j an die App angebunden?

Python Neo4j Driver

from neo4j import GraphDatabase

# 1. Verbindung erstellen
driver = GraphDatabase.driver(
    "bolt://localhost:7687", 
    auth=("neo4j", "password")
)

# 2. Session öffnen und Query ausführen
with driver.session() as session:
    result = session.run("""
        MATCH (m:Museum)-[:LOCATED_IN]->(c:Canton {abbr: $canton})
        RETURN m.name, m.city
    """, {"canton": "ZH"})

    # 3. Ergebnisse verarbeiten
    for record in result:
        print(record["m.name"], record["m.city"])

# 4. Verbindung schließen
driver.close()

Wichtige Konzepte:

  • Driver: Managed Connection Pool
  • Session: Transaktions-Kontext
  • Parametrisierte Queries: $canton verhindert Injection

🎤 Frage 3: Schema-Syntax?

Cypher-Syntax

// Einzelner Node
CREATE (m:Museum {
    name: "Kunsthaus Zürich", 
    city: "Zürich",
    founded: 1787
})

// Mit RETURN
CREATE (m:Museum {name: "Landesmuseum"})
RETURN m
// Nodes matchen und verbinden
MATCH (m:Museum {name: "Kunsthaus Zürich"}), 
      (c:Canton {abbr: "ZH"})
CREATE (m)-[:LOCATED_IN]->(c)

// Mit Properties
CREATE (m)-[:FEATURES {
    description: "Dauerausstellung",
    since: 2020
}]->(p:Person)
// Unique Constraint
CREATE CONSTRAINT museum_name IF NOT EXISTS
FOR (m:Museum) REQUIRE m.name IS UNIQUE

// Node Key (mehrere Properties)
CREATE CONSTRAINT museum_key IF NOT EXISTS
FOR (m:Museum) REQUIRE (m.name, m.city) IS NODE KEY
// Einfaches Pattern
MATCH (m:Museum)-[:LOCATED_IN]->(c:Canton)
WHERE c.name = "Zürich"
RETURN m.name

// Variable Length Path
MATCH (m1:Museum)-[:NEAR*1..3]-(m2:Museum)
WHERE m1.name = "Kunsthaus Zürich"
RETURN m2.name

🎤 Frage 4: Was funktioniert / Probleme / Ausblick?

Erfolgreich implementiert

  • Datenimport: 28 Museen, 15 Kantone, 6 Kategorien, 5 Personen
  • Filter-System: Kanton + Kategorie kombinierbar
  • Detail-Ansicht: Museum-Info + verknüpfte Personen
  • Statistik: Top 10 Kantone nach Museumsanzahl
  • NEAR-Beziehungen: Museen im gleichen Kanton automatisch verknüpft
  • Error Handling: Verbindungsfehler werden abgefangen

Nicht implementiert

Technisch machbar, aber Zeitrahmen:

  • Shortest Path: Routen zwischen Museen

    MATCH path = shortestPath(
      (m1:Museum)-[:NEAR*]-(m2:Museum)
    )
    WHERE m1.name = $start AND m2.name = $end
    RETURN path
    

  • Graph-Visualisierung: Mit matplotlib/networkx

  • Export-Funktion: CSV/JSON-Export der Suchergebnisse
  • Person-Detail-View: Biografie + alle Museen

Herausforderungen

  1. Neo4j-Passwort: Muss manuell konfiguriert werden
  2. First-Time-Setup: User muss Neo4j Desktop selbst starten
  3. Datenmenge: Nur 28 Museen (echte DB hätte 1100+)
  4. tkinter-Limits: Keine native Graph-Visualisierung

Erweiterungsmöglichkeiten

Kurz- bis mittelfristig (1-3 Wochen):

  • 🗺️ Google Maps Integration
  • Koordinaten zu Museen hinzufügen
  • Interactive Map mit Marker

  • 📊 Graph-Visualisierung

  • networkx + matplotlib für Static Graphs
  • Cytoscape.js für Interactive Web-View

  • 📅 Event-Nodes

    CREATE (e:Event {
        name: "Giacometti Retrospektive",
        start_date: date("2024-03-01"),
        end_date: date("2024-06-30")
    })
    CREATE (m:Museum)-[:HOSTS]->(e)
    

Langfristig (1-3 Monate):

  • 🤖 Community Detection
  • Welche Museen bilden thematische Cluster?
  • Graph Data Science Library nutzen

  • 🔍 Semantic Search

  • Volltextsuche in Beschreibungen
  • NLP für ähnliche Museen

  • 🌐 Web-Frontend

  • Flask/FastAPI Backend
  • React Frontend mit D3.js

🛠️ Troubleshooting

Problem: 'Konnte nicht zu Neo4j verbinden'

Lösung:

  1. ✅ Prüfe, ob Neo4j Desktop läuft
  2. ✅ Prüfe, ob die Datenbank gestartet ist (grüner Status)
  3. ✅ Prüfe Passwort in museum_app.py Zeile 20
  4. ✅ Teste Verbindung in Neo4j Browser: http://localhost:7474

Terminal-Test:

python -c "from neo4j import GraphDatabase; \
           driver = GraphDatabase.driver('bolt://localhost:7687', \
           auth=('neo4j', 'password')); \
           driver.verify_connectivity(); \
           print('✓ Verbindung OK')"

Problem: 'Import fehlgeschlagen'

Lösung:

  1. Stelle sicher, dass data_importer.py im gleichen Ordner ist
  2. Prüfe Neo4j-Logs in Desktop (drei Punkte → Logs)
  3. Lösche die Datenbank und erstelle sie neu

Manueller Test in Neo4j Browser:

CREATE (m:Museum {name: "Test"})
RETURN m

Problem: 'Keine Museen gefunden'

Lösung:

  1. Klicke zuerst auf "📥 Datenbank initialisieren"
  2. Warte bis "Import abgeschlossen" in Statusleiste erscheint
  3. Prüfe Filter: "Alle" für Kanton und Kategorie wählen

Prüfe in Neo4j Browser:

MATCH (m:Museum) RETURN count(m)
// Sollte 28 zurückgeben


📁 Dateistruktur

museum-project/
├── museum_app.py          # 🎨 Haupt-GUI-Anwendung (tkinter)
├── data_importer.py       # 📥 Daten-Import-Logik
├── requirements.txt       # 📦 Python-Dependencies
└── README.md             # 📖 Diese Datei

🎓 M165 NoSQL - Evaluation

Projekt-Zusammenfassung

Ziel: Schweizer Museen mit Neo4j-Graphdatenbank verwalten

Technologie-Stack:

  • Backend: Python 3.10+
  • GUI: tkinter (Standard-Library)
  • Datenbank: Neo4j Desktop 5.x
  • Driver: neo4j-python-driver 5.14.0

Zeitaufwand: ~4 Stunden (planbar in 5h)

Lernziele erfüllt:

  • NoSQL-Datenbank praktisch eingesetzt
  • Graph-Modell entworfen und implementiert
  • CRUD-Operationen mit Cypher
  • Python-Anbindung mit offiziellem Driver
  • Unterschiede zu relationalen/dokumentenbasierten DBs verstanden

📚 Weiterführende Ressourcen


Erstellt für: M165 NoSQL 2026 Evaluation
Version: 1.0
Letztes Update: Januar 2026
Lizenz: MIT