場所と地図: 会場の個人カタログ
すべてのイベントには場所が必要です。でも、定期的にイベントを主催しているのは誰ですか レストラン、応接室、友人の家、 公園、ナイトクラブ。で イベントをプレイする places モジュールを使用すると、 個人カタログ 会場の住所、GPS 座標、連絡先、評価、および イベントごとに再利用されるタグ。
この記事でわかること
- Place エンティティとそのフィールド: 住所、座標、連絡先
- 会場を分類するには「Place」と入力します。
- 個人的な評価とお気に入り
- 地縁協会(友人宅)
- EventtoLuogo: 特定の役割を持つイベントと会場を結び付ける
- 素早い検索とフィルタリングのためのタグ
- MapLibre GL JS を使用したインタラクティブなマップ
- Nominatim を使用したジオコーディング (OpenStreetMap)
- グループ内の場所を選ぶためのアンケート
場所エンティティ
Il 場所 およびモジュールの集約ルート。すべてのユーザー 場所を独立して管理します: データベースはありません 共有会場。主催者側の情報によると、 保存(プライベートメモ、評価、平均価格)は主観的であり、 個人的な。
@Entity
@Table(name = "luoghi")
public class Luogo {
private Long id;
private Long utenteId;
// Tipologia (ristorante, sala eventi, casa privata...)
private TipologiaLuogo tipologia;
// Informazioni base
private String nome; // "Ristorante Da Mario"
private String descrizione; // Descrizione pubblica
private String notePrivate; // Note solo per l'organizzatore
// Indirizzo completo
private String indirizzo; // "Via Roma 42"
private String citta; // "Bari"
private String provincia; // "BA"
private String cap; // "70121"
private String paese; // "Italia"
private BigDecimal latitudine; // 41.12560000
private BigDecimal longitudine; // 16.86990000
// Contatti
private String telefono;
private String email;
private String sitoWeb;
// Dettagli
private Integer capienzaMax; // 150 persone
private BigDecimal prezzoMedio; // 35.00
private String valuta; // "EUR"
// Valutazione e preferiti
private Integer valutazione; // 1-5 stelle
private Boolean preferito; // Accesso rapido
// Link a collegamento
private Long collegamentoId; // "Casa di Marco"
// Metadati
private String immagineUrl;
private String tags; // "matrimonio,elegante,vista-mare"
}
ファクトリーメソッド Luogo.crea(utenteId, tipologia, nome)
デフォルト値を設定します: 国「イタリア」、通貨「ユーロ」、およびお気に入り
ある false。他のすべてのフィールドは次のように更新されます。
専用のビジネスメソッド。
会場タイプ: 会場を分類する
すべての場所は 1 つに属します タイププレイス: レストラン、 イベントルーム、民家、公園、ナイトクラブ。種類は次のとおりです。 多言語サポート (イタリア語と英語) を備えた永続エンティティ、 アイコン、色、カスタマイズ可能な並べ替え。
@Entity
@Table(name = "tipologie_luogo")
public class TipologiaLuogo {
private Long id;
private String codice; // "RISTORANTE"
private String nomeIt; // "Ristorante"
private String nomeEn; // "Restaurant"
private String descrizioneIt; // Descrizione in italiano
private String descrizioneEn; // Descrizione in inglese
private String icona; // "MapPin" (icona Lucide)
private String colore; // "#6366f1"
private Integer ordine; // Per ordinamento nel menu
private Boolean attiva; // Tipologia abilitata
public String getNome(String lingua) {
return "en".equalsIgnoreCase(lingua) ? nomeEn : nomeIt;
}
}
多言語サポートはエンティティに直接統合されています。方法
getNome(lingua) 名前を正しい言語で返します
個別の変換テーブルは必要ありません。類型論でできることは、
削除せずに非アクティブ化し、一貫性を維持します。
すでに分類されている場所。
種類の例
- レストラン: レストラン、トラットリア、ピッツェリア
- イベントルーム: 応接室、会議スペース
- プライベートハウス: 友人や家族の私邸
- 夜_ローカル: パブ、ディスコ、カクテルバー
- オープンスペース: 公園、庭園、ビーチ
- ホテル: ホテル・宿泊施設
- 劇場: 劇場、映画館、講堂
- 他の: 予測されていないタイプ
住所とジオコーディング
どの場所にも 構造化されたアドレス ストリートで構成されており、
市、県、郵便番号、国。さらに、システムは
GPS座標は次のようになります BigDecimal 高精度で
(緯度 10.8、経度 11.8 桁)。
// Aggiorna indirizzo completo con coordinate GPS
luogo.aggiornaIndirizzo(
"Via Roma 42", // indirizzo
"Bari", // citta
"BA", // provincia
"70121", // cap
"Italia", // paese
new BigDecimal("41.12560000"), // latitudine
new BigDecimal("16.86990000") // longitudine
);
// Metodo helper per indirizzo formattato
luogo.getIndirizzoCompleto();
// Risultato: "Via Roma 42, Bari (BA) - 70121"
// Query utili
luogo.hasIndirizzo(); // true se indirizzo presente
luogo.hasCoordinate(); // true se lat/lng presenti
座標はユーザーが手動で入力するものではありません。いつ 主催者がアドレスを入力し、フロントエンドがリクエストを送信します。 として機能するバックエンドへ Nominatim に対する代理人 (OpenStreetMap ジオコーディング サービス)。返された座標 アドレスとともに自動的に保存されます。
連絡先と詳細
場所が持つことができるのは、 連絡先 (電話、メール、ウェブサイト) e 運用の詳細 最大容量と平均価格として。 すべての場所で必要なわけではないため、これらのフィールドはすべてオプションです。 友人の家にはウェブサイトがありませんし、公園には明確な収容人数がありません。
// Contatti
luogo.aggiornaContatti(
"+39 080 1234567", // telefono
"info@ristorantedamario.it", // email
"https://www.ristorantedamario.it" // sito web
);
// Dettagli con validazione
luogo.aggiornaDettagli(
150, // capienzaMax (non negativa)
new BigDecimal("35.00"), // prezzoMedio (non negativo)
"EUR" // valuta (default EUR)
);
// Il sistema valida automaticamente:
// - capienzaMax >= 0
// - prezzoMedio >= 0
// - valuta non null (default "EUR")
個人的な評価: 1 ~ 5 つ星
各場所で 1 つ受信できます 個人的な評価 1 つ星から 5 つ星まで。これはトリップアドバイザーのような公的評価ではありません。 及び主催者の主観的判断によるものです。 経験。レストランは Google で 5 つ星を獲得しているかもしれませんが、 団体向けサービスのため主催者より星2つを頂きました 遅かったです。
// Valuta il luogo (1-5 stelle)
luogo.valuta(4); // 4 stelle
// Rimuovi la valutazione
luogo.valuta(null);
// Query
luogo.hasValutazione(); // true/false
// Validazione automatica:
// - stelle deve essere tra 1 e 5
// - null e ammesso (nessuna valutazione)
luogo.valuta(0); // IllegalArgumentException
luogo.valuta(6); // IllegalArgumentException
// Indice DB per ricerca per valutazione
@Index(name = "idx_luoghi_valutazione",
columnList = "utente_id, valutazione")
データベース上のインデックス idx_luoghi_valutazione 許可します
評価によって場所をすばやくフィルタリングするには: 「すべてを表示」
私のレストランは4つ星か5つ星です。」
お気に入り: クイックアクセス
よく使う場所は、 としてマークされています お気に入り。優先フラグを使用すると、迅速にフィルタリングできます。 イベントを作成するときに最もよく使用される場所。
// Segna come preferito
luogo.segnaPreferito();
// Rimuovi dai preferiti
luogo.rimuoviPreferito();
// Toggle (inverti lo stato)
luogo.togglePreferito();
// Query
luogo.isPreferito(); // true/false
// Indice DB per accesso rapido
@Index(name = "idx_luoghi_preferito",
columnList = "utente_id, preferito")
関係へのリンク
ユニークな機能 イベントをプレイする そしてその可能性 場所を関係に結びつける。 場所が友人の家である場合、主催者はそれを関連付けることができます。 対応するリンクに配置してください。それで、場所を探すときは、 イベントの場合、システムは関連する関係の名前も表示します。 「マルコの家(Via delle Rose 15, Lecce)」。
// Associa il luogo a un collegamento
luogo.associaCollegamento(collegamentoId);
// Esempio: il luogo "Casa in campagna" viene associato
// al collegamento "Marco Rossi"
// Rimuovi l'associazione
luogo.rimuoviCollegamento();
// Query
luogo.isAssociatoACollegamento(); // true/false
// Nel frontend, quando collegamentoId e presente,
// il sistema carica anche il nome del collegamento
// per mostrarlo nella card del luogo
EventoLuogo: 会場とイベントを関連付ける
出来事と場所の関係 多対多:a
イベントには複数の場所が存在する場合があります(教会の式典、レセプション)
レストラン、ホテル宿泊施設)と同じ場所をご利用いただけます
さまざまなイベントに。ブリッジテーブル EventoLuogo 管理する
この関連付けと追加フィールド: 一種の
使用法.
public enum TipoUtilizzoLuogo {
PRINCIPALE("Principale"), // Location principale
SECONDARIO("Secondario"), // Location aggiuntiva
CERIMONIA("Cerimonia"), // Per la cerimonia (matrimoni)
RICEVIMENTO("Ricevimento"), // Per il ricevimento
PERNOTTAMENTO("Pernottamento"),// Per gli ospiti
PARCHEGGIO("Parcheggio"), // Area parcheggio
PUNTO_RITROVO("Punto Ritrovo") // Dove trovarsi
}
たとえば、結婚式には 4 つの場所が関連付けられます: 教会 セレモニーとして、レストランとしてレセプション、ホテルとして 宿泊と駐車場としての駐車。各協会 イベント固有のメモが含まれる場合もあります。
// Associa un luogo come venue principale
EventoLuogo principale = EventoLuogo.creaPrincipale(evento, ristorante);
// Associa con tipo specifico e note
EventoLuogo cerimonia = EventoLuogo.crea(
evento,
chiesa,
TipoUtilizzoLuogo.CERIMONIA,
"Ingresso laterale, parcheggio in Piazza Garibaldi"
);
// Query
cerimonia.isPrincipale(); // false
cerimonia.getEventoId(); // ID dell'evento
cerimonia.getLuogoId(); // ID del luogo
// Cambio tipo di utilizzo
cerimonia.aggiornaTipoUtilizzo(TipoUtilizzoLuogo.PRINCIPALE);
cerimonia.aggiornaNote("Nuovo ingresso principale");
一意性制約
組み合わせ (evento_id, luogo_id) そしてユニーク:同じ
場所を同じイベントに 2 回関連付けることはできません。必要に応じて
使用の種類を変更すると、既存の関連付けが更新されます
新しいものを作成するのではなく。
タグ: 検索とフィルター
どの場所にも神がいる可能性がある タグ カンマで区切ります。タグ これらにより、事前に定義された類型を超えた柔軟な検索が可能になります。 「海の景色」、「庭園」、「子供向け」、「Wi-Fi」、「専用駐車場」。
// Imposta i tag
luogo.impostaTags("matrimonio,elegante,vista-mare,giardino");
// I tag sono salvati come stringa separata da virgola
// Il frontend li divide e li mostra come chip/badge
// Ricerca per tag (lato repository):
// SELECT * FROM luoghi
// WHERE utente_id = :utenteId
// AND tags LIKE '%vista-mare%'
MapLibre GL JS を使用したインタラクティブな地図
Angular フロントエンドで イベントをプレイする、 ~のおかげで場所が生き返る MapLibre GL JS、1つ ベクター マップ用のオープンソース ライブラリ。すべての場所と座標 インタラクティブなポップアップを備えた地図上のマーカーとして表示されます 名前、タイプ、評価を表示します。
ロケーションマップの機能
- 色付きマーカー タイプ別(レストランは赤、イベントルームは青など)
- クラスタリング 近くに多くの場所がある場合は自動
- インタラクティブなポップアップ クリックすると場所の詳細が表示されます
- フィルター可能なレイヤー タイプ、評価、タグ別
- ルートライン イベント会場間
- 地理位置情報 現在地に近い場所を表示するには
- パーソナライズされたスタイル アプリのデザインと一致したダークモード
Google マップと比較した MapLibre GL JS の選択は戦略的です: なし リクエストあたりのコスト、マップ スタイルの完全なカスタマイズ OpenStreetMap のような無料のタイル サーバーとの互換性。レンダリング Vector は、数百のマーカーを使用しても高いパフォーマンスを保証します。
FRONTEND ANGULAR
│
├── MapComponent
│ ├── Inizializzazione MapLibre GL JS
│ ├── Stile: Dark Mode custom (Positron Dark / OSM Liberty)
│ └── Controlli: Zoom, Rotazione, Geolocalizzazione
│
├── Markers Layer
│ ├── Icone per TipologiaLuogo (colore + forma)
│ ├── Clustering automatico (zoom < 12)
│ └── Popup con nome, tipo, valutazione (stelle)
│
├── Route Layer (per eventi multi-luogo)
│ ├── Linee tra venue dello stesso evento
│ └── Ordine: CERIMONIA → RICEVIMENTO → PERNOTTAMENTO
│
└── Interazione
├── Click su marker → Apri dettaglio luogo
├── Click su mappa → Reverse geocoding (nuovo luogo)
└── Drag marker → Aggiorna coordinate
Nominatim を使用したジオコーディング
Il ジオコーディング (アドレスから座標への変換) および管理 バックエンドプロキシ経由で 名前付き、サービス オープンストリートマップの。バックエンドが必要な理由は 2 つあります。 レート制限 (1 秒あたり最大 1 リクエスト) を設定し、 Nominatim ポリシーで要求されるユーザー エージェント識別子。
// Endpoint REST
GET /api/v1/geocoding/search?q=Via Roma 42, Bari&limit=5
GET /api/v1/geocoding/reverse?lat=41.1256&lng=16.8699
// NominatimClient: gestione rate limiting thread-safe
private static final long MIN_INTERVALLO_MS = 1100; // 1.1 secondi
private final ReentrantLock rateLimitLock = new ReentrantLock();
private void rispettaRateLimit() {
rateLimitLock.lock();
try {
long tempoTrascorso = System.currentTimeMillis()
- ultimaRichiestaTimestamp;
if (tempoTrascorso < MIN_INTERVALLO_MS) {
Thread.sleep(MIN_INTERVALLO_MS - tempoTrascorso);
}
ultimaRichiestaTimestamp = System.currentTimeMillis();
} finally {
rateLimitLock.unlock();
}
}
// Risposta formattata
{
"latitudine": "41.1256000",
"longitudine": "16.8699000",
"nomeVisualizzato": "Via Roma 42, Bari, BA, Italia",
"indirizzo": {
"via": "Via Roma",
"numeroCivico": "42",
"citta": "Bari",
"provincia": "Bari",
"cap": "70121",
"paese": "Italia"
}
}
レート制限とユーザーエージェント
Nominatim は無料のサービスで、基本的なルールは 1 つあります。それは「最大値」です。
1 秒あたり 1 リクエスト。の NominatimClient
を使用します ReentrantLock 制限を確実に遵守するために
マルチスレッドのシナリオでも。 User-Agent ヘッダーは必須です。
アプリケーションを識別します。
"PlayTheEvent/1.0 (info@playtheevent.com)".
立地選定調査
グループがイベントを開催する場所を決定する必要がある場合、
世論調査 が登場します。エンティティの種類
TipoEntitaSondaggio.LUOGO アンケートを作成できます
ここで、オプションは主催者のカタログからの場所です。
// Crea un sondaggio per scegliere il luogo
Sondaggio sondaggio = Sondaggio.creaPerLuoghi(
evento,
organizzatore,
"Dove facciamo la cena di compleanno?"
);
// Aggiungi luoghi come opzioni
sondaggio.aggiungiOpzioneLuogo(
"Ristorante Da Mario", // testo
luogoMarioId, // luogoId
"Vista mare, cucina pugliese, 35 EUR/persona",
"https://..." // immagine
);
sondaggio.aggiungiOpzioneLuogo(
"Trattoria La Nonna",
luogoNonnaId,
"Cucina casalinga, giardino esterno, 25 EUR/persona",
"https://..."
);
// Pubblica il sondaggio
sondaggio.pubblica();
// Dopo le votazioni, chiudi e seleziona il vincitore
sondaggio.chiudi();
sondaggio.selezionaVincitore(opzioneVincitriceId, organizzatore, null);
// Il luogo vincitore può essere automaticamente
// associato all'evento come venue principale
PLACE タイプのアンケートには、次のような独自の機能があります。 勝者の選択。調査が来たら クローズされた場合、主催者は勝者オプションを選択できます( 通常、最も投票されたものに対応します) とシステムが関連付けます イベントのその場所が自動的に設定されます。
場所調査の特徴
- 現実の場所とリンクしたオプション 写真、説明、座標付き
- パブリック共有 固有のコード (UUID) を持つリンク経由
- 勝者の選択 変更履歴と理由付き
- 再利用可能なテンプレート 定期的なアンケートの場合
- 匿名または名目による投票、投票と結果をリアルタイムで変更します
指数とパフォーマンス
テーブル luoghi を保証するための戦略的指標があります。
最も一般的なクエリでの高いパフォーマンス: ユーザーによる検索、
タイプによるフィルター、都市による検索、お気に入りへのアクセス、
評価順に並べ替えます。
@Table(name = "luoghi", indexes = {
// Tutti i luoghi di un utente
@Index(name = "idx_luoghi_utente",
columnList = "utente_id"),
// Filtro per tipologia
@Index(name = "idx_luoghi_tipologia",
columnList = "tipologia_id"),
// Ricerca per citta
@Index(name = "idx_luoghi_citta",
columnList = "citta"),
// Preferiti dell'utente
@Index(name = "idx_luoghi_preferito",
columnList = "utente_id, preferito"),
// Ordinamento per valutazione
@Index(name = "idx_luoghi_valutazione",
columnList = "utente_id, valutazione")
})
重要なポイント
- 会場は共有データベースではなく、主催者の個人カタログです
- TipologiaLuogo はアイコンと色による多言語分類を提供します
- GPS 座標は高精度のために BigDecimal として保存されます
- 場所と関係のつながりが文脈を豊かにする (「マルコの家」)
- EventoLuogo は、特定の使用タイプとの多対多の関係を管理します
- MapLibre GL JS は、マーカー、クラスタリング、ルート レイヤーを使用して場所を視覚化します
- Nominatim は、バックエンドによって管理されるレート制限を備えた無料のジオコーディングを提供します
- LOCATION 投票では、グループが勝者を選択して会場に投票することができます
ソースコードは次の場所から入手できます。 GitHub。 実際の Place モジュールを探索するには、次のサイトにアクセスしてください。 www.playtheevent.com.







