장소 및 지도: 장소의 개인 카탈로그
모든 이벤트에는 장소가 필요합니다. 하지만 정기적으로 이벤트를 조직하는 사람은 누구입니까? 레스토랑, 응접실, 친구 집 등 수십 개의 장소가 축적되어 있습니다. 공원, 나이트클럽. ~ 안에 이벤트를 플레이하세요 장소 모듈을 사용하면 개인 카탈로그 주소, GPS 좌표, 연락처, 평점 및 정보가 포함된 장소 정보 태그는 이벤트 이후에 재사용됩니다.
이 기사에서 찾을 수 있는 내용
- 장소 엔터티 및 해당 필드: 주소, 좌표, 연락처
- 장소를 분류하려면 Place를 입력하세요.
- 개인 평가 및 즐겨찾기
- 장소연계연대(내 친구집)
- EventoLuogo: 특정 역할이 있는 이벤트에 장소를 연결합니다.
- 빠른 검색 및 필터링을 위한 태그
- 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)
기본값 설정: 국가 "이탈리아", 통화 "EUR" 및 즐겨찾기
에 false. 다른 모든 필드는 다음을 통해 업데이트됩니다.
전용 비즈니스 방법.
장소 유형: 장소 분류
모든 장소는 하나의 장소에 속합니다. 유형장소: 레스토랑, 이벤트 룸, 개인 주택, 공원, 나이트 클럽. 유형은 다음과 같습니다. 다국어 지원(이탈리아어 및 영어)을 갖춘 영구 엔터티 아이콘, 색상 및 사용자 정의 가능한 정렬.
@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) 올바른 언어로 이름을 반환합니다.
별도의 번역표가 필요하지 않습니다. 유형은 다음과 같습니다.
삭제하지 않고 비활성화하여 일관성을 유지합니다.
이미 분류된 장소.
유형의 예
- 식당: 레스토랑, 트라토리아, 피자 가게
- EVENT_ROOM: 응접실, 회의 공간
- PRIVATE_HOUSE: 친구나 가족의 개인 주택
- 밤_현지: 펍, 디스코, 칵테일 바
- OPEN_SPACE: 공원, 정원, 해변
- 호텔: 호텔 및 숙박 시설
- 극장: 극장, 영화관, 강당
- 다른: 예상하지 못한 유형
주소 및 지오코딩
모든 장소에는 구조화된 주소 거리로 구성되어 있으며,
시, 도, 우편번호 및 국가. 또한 시스템은
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 지오코딩 서비스) 반환된 좌표 주소와 함께 자동으로 저장됩니다.
연락처 및 세부정보
장소는 가질 수 있습니다 콘택트 렌즈 (전화, 이메일, 웹사이트) 전자 운영 세부정보 최대 용량과 평균 가격으로. 모든 장소에서 요구되는 것은 아니므로 다음 필드는 모두 선택 사항입니다. 친구 집에는 웹사이트가 없고, 공원에는 정해진 수용 인원이 없습니다.
// 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개부터 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: 이벤트와 장소 연결
사건과 장소의 관계 e 다대다: 에
행사는 여러 장소에서 열릴 수 있습니다(교회 예식, 리셉션
레스토랑, 호텔 숙박)과 같은 장소 이용 가능
다양한 이벤트를 위해. 브릿지 테이블 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가지 장소가 있을 수 있습니다. CEREMONY로 레스토랑을 RECEPTION으로 호텔을 하룻밤 숙박 및 주차를 주차로 합니다. 모든 협회 이벤트별 메모도 있을 수 있습니다.
// 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) 고유함: 동일
장소는 동일한 이벤트와 두 번 연결될 수 없습니다. 필요한 경우
사용 유형을 변경하면 기존 연결이 업데이트됩니다.
새로 만드는 것보다.
태그: 검색 및 필터
모든 장소에는 신이 있을 수 있다 태그 쉼표로 구분됩니다. 태그 미리 정의된 유형을 넘어서는 유연한 검색이 가능합니다. "바다 전망", "정원", "어린이에게 적합", "와이파이", "전용 주차장".
// 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 프론트엔드에서 이벤트를 플레이하세요, 덕분에 장소가 살아납니다. 맵리브레 GL JS, 하나 벡터 지도용 오픈 소스 라이브러리. 좌표가 있는 모든 위치 대화형 팝업과 함께 지도에 마커로 표시됩니다. 이름, 유형 및 등급을 표시합니다.
위치 지도 기능
- 컬러 마커 유형별(빨간색은 레스토랑, 파란색은 이벤트룸 등)
- 클러스터링 근처에 장소가 많을 때 자동으로
- 대화형 팝업 클릭 시 장소에 대한 세부정보 포함
- 필터링 가능한 레이어 유형, 등급 또는 태그별
- 노선 이벤트 장소 사이
- 지리적 위치 현재 위치 근처의 장소를 표시하려면
- 개인화된 스타일 앱 디자인과 일치하는 다크 모드
Google Maps와 비교하여 MapLibre GL JS를 선택하는 것은 전략적입니다. 요청당 비용, 지도 스타일의 전체 사용자 정의 OpenStreetMap과 같은 무료 타일 서버와의 호환성. 렌더링 벡터는 수백 개의 마커에도 높은 성능을 보장합니다.
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 지오코딩 (주소-좌표 변환) 및 관리 백엔드 프록시를 통해 명명됨, 서비스 오픈스트리트맵의 백엔드는 두 가지 이유로 필요합니다. 속도 제한(초당 최대 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개. 그만큼 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. 실제 장소 모듈을 살펴보려면 다음을 방문하세요. www.playtheevent.com.







