]> git.openstreetmap.org Git - nominatim-ui.git/blob - src/pages/DetailsPage.svelte
detail page: add country to postcode -search by name- link
[nominatim-ui.git] / src / pages / DetailsPage.svelte
1 <script>
2   import { fetch_from_api, update_html_title } from '../lib/api_utils.js';
3   import { page } from '../lib/stores.js';
4
5   import {
6     osmLink, wikipediaLink, coverageType, isAdminBoundary,
7     formatAddressRank, formatKeywordToken, formatOSMType
8   } from '../lib/helpers.js';
9   import Header from '../components/Header.svelte';
10   import MapIcon from '../components/MapIcon.svelte';
11   import SearchSectionDetails from '../components/SearchSectionDetails.svelte';
12   import DetailsOneRow from '../components/DetailsOneRow.svelte';
13   import DetailsLink from '../components/DetailsLink.svelte';
14   import DetailsPostcodeHint from '../components/DetailsPostcodeHint.svelte';
15   import InfoRow from '../components/DetailsInfoRow.svelte';
16   import InfoRowList from '../components/DetailsInfoRowList.svelte';
17   import Map from '../components/Map.svelte';
18
19   let aPlace;
20   let base_url;
21   let api_request_params;
22   let api_request_finished = false;
23
24   function loaddata(search_params) {
25     api_request_params = {
26       place_id: search_params.get('place_id'),
27       osmtype: search_params.get('osmtype'),
28       osmid: search_params.get('osmid'),
29       class: search_params.get('class'),
30       keywords: search_params.get('keywords'),
31       addressdetails: 1,
32       hierarchy: (search_params.get('hierarchy') === '1' ? 1 : 0),
33       group_hierarchy: 1,
34       polygon_geojson: 1,
35       format: 'json'
36     };
37     api_request_finished = false;
38
39     if (api_request_params.place_id || (api_request_params.osmtype && api_request_params.osmid)) {
40
41       if (api_request_params.place_id) {
42         update_html_title('Details for ' + api_request_params.place_id);
43       } else {
44         update_html_title('Details for ' + api_request_params.osmtype + api_request_params.osmid);
45       }
46
47       fetch_from_api('details', api_request_params, function (data) {
48         window.scrollTo(0, 0);
49         api_request_finished = true;
50         aPlace = (data && !data.error) ? data : undefined;
51       });
52     } else {
53       aPlace = undefined;
54     }
55   }
56
57   function place_has_keywords(aThisPlace) {
58     // Return false if Nominatim API sends 'keywords: { name: [], address: [] }'
59     // Like no longer needed after Nominatim version 4.3
60     return (
61       aThisPlace.keywords && aThisPlace.keywords.name && aThisPlace.keywords.address
62       && (aThisPlace.keywords.name.length > 0 || aThisPlace.keywords.address.length > 0)
63     );
64   }
65
66   function country_code(aThisPlace) {
67     let aLine = aThisPlace.address.find((address_line) => address_line.type === 'country_code');
68     return aLine ? aLine.localname : null;
69   }
70
71   $: {
72     let pageinfo = $page;
73     if (pageinfo.tab === 'details') {
74       loaddata(pageinfo.params);
75       base_url = window.location.search;
76     }
77   }
78   $: reverse_only = Nominatim_Config.Reverse_Only;
79 </script>
80
81 <Header>
82   <SearchSectionDetails api_request_params={api_request_params}/>
83 </Header>
84
85 <div class="container">
86   {#if aPlace}
87     <div class="row">
88       <div class="col-sm-10">
89         <h1>
90           {aPlace.localname || `${formatOSMType(aPlace.osm_type)} ${aPlace.osm_id}` }
91           <small><DetailsLink feature={aPlace}>link to this page</DetailsLink></small>
92         </h1>
93       </div>
94       <div class="col-sm-2 text-end">
95         <MapIcon aPlace={aPlace} />
96       </div>
97     </div>
98     <div class="row">
99       <div class="col-md-6">
100         <table id="locationdetails" class="table table-striped table-responsive">
101           <tbody>
102             <InfoRow title="Name">
103             {#if aPlace.names && typeof (aPlace.names) === 'object'
104               && Object.keys(aPlace.names).length}
105               <InfoRowList items={aPlace.names} />
106             {:else}
107               <span class="noname fw-bold">No Name</span>
108             {/if}
109             </InfoRow>
110             <InfoRow title="Type">{aPlace.category}:{aPlace.type}</InfoRow>
111             <InfoRow title="Last Updated">{aPlace.indexed_date}</InfoRow>
112             {#if (isAdminBoundary(aPlace)) }
113               <InfoRow title="Admin Level">{aPlace.admin_level}</InfoRow>
114             {/if}
115             <InfoRow title="Search Rank">{aPlace.rank_search}</InfoRow>
116             <InfoRow title="Address Rank">
117               {aPlace.rank_address} ({formatAddressRank(aPlace.rank_address)})
118             </InfoRow>
119             {#if aPlace.calculated_importance}
120               <InfoRow title="Importance">
121                   {aPlace.calculated_importance}
122                   {#if !aPlace.importance} (estimated){/if}
123               </InfoRow>
124             {/if}
125             <InfoRow title="Coverage">{coverageType(aPlace)}</InfoRow>
126             <InfoRow title="Centre Point (lat,lon)">
127                 {aPlace.centroid.coordinates[1]},{aPlace.centroid.coordinates[0]}
128             </InfoRow>
129             <!-- eslint-disable-next-line svelte/no-at-html-tags -->
130             <InfoRow title="OSM">{@html osmLink(aPlace)}</InfoRow>
131             <InfoRow title="Place Id">
132                {aPlace.place_id}
133                (<a href="https://nominatim.org/release-docs/develop/api/Output/#place_id-is-not-a-persistent-id">
134                  on this server
135                </a>)
136             </InfoRow>
137             {#if aPlace.calculated_wikipedia}
138               <!-- eslint-disable-next-line svelte/no-at-html-tags -->
139               <InfoRow title="Wikipedia Calculated">{@html wikipediaLink(aPlace)}</InfoRow>
140             {/if}
141             <InfoRow title="Computed Postcode">
142               {#if aPlace.calculated_postcode}
143                 {aPlace.calculated_postcode}
144                 <DetailsPostcodeHint postcode={aPlace.calculated_postcode}
145                                      lat={aPlace.centroid.coordinates[1]}
146                                      lon={aPlace.centroid.coordinates[0]} />
147               {/if}
148             </InfoRow>
149             <InfoRow title="Address Tags"><InfoRowList items={aPlace.addresstags} /></InfoRow>
150             <InfoRow title="Extra Tags"><InfoRowList items={aPlace.extratags} /></InfoRow>
151           </tbody>
152         </table>
153       </div>
154       <div class="col-md-6">
155         <div id="map-wrapper">
156           <Map current_result={aPlace} />
157         </div>
158       </div>
159     </div>
160     <div class="row">
161       <div class="col-md-12">
162         <h2>Address</h2>
163         <table id="address" class="table table-striped table-small">
164           <thead>
165             <tr>
166               <th>Local name</th>
167               <th>Type</th>
168               <th>OSM</th>
169               <th>Address rank</th>
170               <th>Admin level</th>
171               <th>Distance</th>
172               <th></th>
173             </tr>
174           </thead>
175           <tbody>
176             {#if aPlace.address}
177               {#each aPlace.address as addressLine}
178                 <DetailsOneRow addressLine={addressLine}
179                                bMarkUnusedLines={true}
180                                bDistanceInMeters={false}
181                                sCountryCode={country_code(aPlace)} />
182               {/each}
183             {/if}
184
185             {#if aPlace.linked_places}
186               <tr class="all-columns"><td colspan="7"><h2>Linked Places</h2></td></tr>
187               {#each aPlace.linked_places as addressLine}
188                 <DetailsOneRow addressLine={addressLine}
189                                bMarkUnusedLines={true}
190                                bDistanceInMeters={true} />
191               {/each}
192             {/if}
193
194             {#if !reverse_only}
195               <tr class="all-columns"><td colspan="7"><h2>Keywords</h2></td></tr>
196               {#if api_request_params.keywords}
197
198                 {#if place_has_keywords(aPlace)}
199                   <tr class="all-columns"><td colspan="7"><h3>Name Keywords</h3></td></tr>
200                   {#each aPlace.keywords.name as keyword}
201                     <tr>
202                       <td>{formatKeywordToken(keyword.token)}</td>
203                       {#if keyword.id}
204                         <td>word id: {keyword.id}</td>
205                       {/if}
206                     </tr>
207                   {/each}
208
209                   {#if aPlace.keywords.address}
210                     <tr class="all-columns"><td colspan="7"><h3>Address Keywords</h3></td></tr>
211                     {#each aPlace.keywords.address as keyword}
212                       <tr>
213                         <td>{formatKeywordToken(keyword.token)}</td>
214                         {#if keyword.id}
215                           <td>word id: {keyword.id}</td>
216                         {/if}
217                       </tr>
218                     {/each}
219                   {/if}
220                 {:else}
221                   <tr><td>Place has no keywords</td></tr>
222                 {/if}
223               {:else}
224                 <tr>
225                   <td>
226                      <a class="btn btn-outline-secondary btn-sm"
227                       href="{base_url}&keywords=1">display keywords</a>
228                   </td>
229                 </tr>
230               {/if}
231             {/if}
232
233             <tr class="all-columns"><td colspan="7"><h2>Parent Of</h2></td></tr>
234             {#if api_request_params.hierarchy}
235               {#if aPlace.hierarchy && typeof (aPlace.hierarchy) === 'object'
236                 && Object.keys(aPlace.hierarchy).length}
237                 {#each Object.keys(aPlace.hierarchy) as type}
238                   <tr class="all-columns"><td colspan="7"><h3>{type}</h3></td></tr>
239                   {#each aPlace.hierarchy[type] as line}
240                     <DetailsOneRow addressLine={line} bDistanceInMeters={true} />
241                  {/each}
242                 {/each}
243
244                 {#if Object.keys(aPlace.hierarchy) > 500}
245                   <p>There are more child objects which are not shown.</p>
246                 {/if}
247               {:else}
248                 <tr><td>Place is not parent of other places</td></tr>
249               {/if}
250             {:else}
251               <tr>
252                 <td>
253                    <a class="btn btn-outline-secondary btn-sm"
254                     href="{base_url}&hierarchy=1">display child places</a>
255                 </td>
256               </tr>
257             {/if}
258           </tbody>
259         </table>
260       </div>
261     </div>
262   {:else if (window.location.search !== '' && api_request_finished)}
263     No such place found.
264   {/if}
265 </div>
266
267
268
269 <style>
270   h1 {
271     margin: 10px 0;
272     padding-left: 8px;
273   }
274
275   h1 small :global(a) {
276     font-size: 0.5em;
277     white-space: nowrap;
278   }
279
280   h2 {
281     font-size: 2em;
282     padding-left: 8px;
283     background-color: white;
284   }
285   h3 {
286     font-size: 1.5em;
287     padding-left: 8px;
288   }
289
290   tr.all-columns {
291     background-color: white !important;
292     border: none;
293   }
294   tr.all-columns td {
295     border-top: none !important;
296     padding-left: 0 !important;
297   }
298   :global(span.noname){
299     color:#800;
300   }
301
302   #map-wrapper {
303     position: relative;
304     width:100%;
305     min-height: auto;
306     height:300px;
307     border: 1px solid #666;
308   }
309 </style>