@@ -187,7 +187,7 @@ OSM.Query = function (map) {
187187 }
188188 }
189189
190- function runQuery ( query , $section , merge , compare ) {
190+ function runQuery ( query , $section , merge , compare , dateFilter ) {
191191 const $ul = $section . find ( "ul" ) ;
192192
193193 $ul . empty ( ) ;
@@ -198,6 +198,7 @@ OSM.Query = function (map) {
198198 }
199199
200200 $section . data ( "ajax" , new AbortController ( ) ) ;
201+
201202 fetch ( OSM . OVERPASS_URL , {
202203 method : "POST" ,
203204 body : new URLSearchParams ( {
@@ -208,7 +209,7 @@ OSM.Query = function (map) {
208209 } )
209210 . then ( response => response . json ( ) )
210211 . then ( function ( results ) {
211- let elements = results . elements ;
212+ let elements = results . elements || [ ] ;
212213
213214 $section . find ( ".loader" ) . hide ( ) ;
214215
@@ -228,6 +229,11 @@ OSM.Query = function (map) {
228229 } , { } ) ) ;
229230 }
230231
232+ // Apply JavaScript date filtering if needed (for dates < 1000 CE)
233+ if ( dateFilter ) {
234+ elements = filterByDate ( elements , dateFilter ) ;
235+ }
236+
231237 if ( compare ) {
232238 elements = elements . sort ( compare ) ;
233239 }
@@ -284,25 +290,81 @@ OSM.Query = function (map) {
284290 return ( maxlon - minlon ) * ( maxlat - minlat ) ;
285291 }
286292
293+ // Check if date is before 1000 CE (OverpassQL doesn't support these dates)
294+ function isBeforeYear1000 ( dateStr ) {
295+ if ( ! dateStr ) return false ;
296+ const match = dateStr . match ( / ^ ( - ? \d + ) / ) ;
297+ return match ? parseInt ( match [ 1 ] , 10 ) < 1000 : false ;
298+ }
299+
300+ // Compare ISO 8601 dates correctly (handles BCE dates where string comparison fails)
301+ function compareDates ( date1 , date2 ) {
302+ if ( ! date1 && ! date2 ) return 0 ;
303+ if ( ! date1 ) return - 1 ;
304+ if ( ! date2 ) return 1 ;
305+
306+ const match1 = date1 . match ( / ^ ( - ? \d + ) (?: - ( \d { 1 , 2 } ) ) ? (?: - ( \d { 1 , 2 } ) ) ? / ) ;
307+ const match2 = date2 . match ( / ^ ( - ? \d + ) (?: - ( \d { 1 , 2 } ) ) ? (?: - ( \d { 1 , 2 } ) ) ? / ) ;
308+ if ( ! match1 || ! match2 ) return date1 . localeCompare ( date2 ) ;
309+
310+ const [ , year1Str , month1Str , day1Str ] = match1 ;
311+ const [ , year2Str , month2Str , day2Str ] = match2 ;
312+ const year1Int = parseInt ( year1Str , 10 ) ;
313+ const year2Int = parseInt ( year2Str , 10 ) ;
314+
315+ if ( year1Int !== year2Int ) return year1Int - year2Int ;
316+
317+ const month1 = month1Str ? parseInt ( month1Str , 10 ) : 1 ;
318+ const month2 = month2Str ? parseInt ( month2Str , 10 ) : 1 ;
319+ if ( month1 !== month2 ) return month1 - month2 ;
320+
321+ const day1 = day1Str ? parseInt ( day1Str , 10 ) : 1 ;
322+ const day2 = day2Str ? parseInt ( day2Str , 10 ) : 1 ;
323+ return day1 - day2 ;
324+ }
325+
326+ function filterByDate ( elements , currentDate ) {
327+ if ( ! currentDate ) return elements ;
328+ return elements . filter ( element => {
329+ const tags = element . tags || { } ;
330+ const startDate = tags . start_date ;
331+ const endDate = tags . end_date ;
332+ if ( startDate && compareDates ( startDate , currentDate ) > 0 ) return false ;
333+ if ( endDate && compareDates ( endDate , currentDate ) <= 0 ) return false ;
334+ return true ;
335+ } ) ;
336+ }
337+
287338 /*
288- * To find nearby objects we ask overpass for the union of the
289- * following sets:
339+ * QUERY MECHANISM:
290340 *
341+ * To find nearby objects we ask Overpass for the union of the following sets:
291342 * node(around:<radius>,<lat>,<lng>)
292343 * way(around:<radius>,<lat>,<lng>)
293344 * relation(around:<radius>,<lat>,<lng>)
294345 *
295- * to find enclosing objects we first find all the enclosing areas:
296- *
346+ * To find enclosing objects we first find all the enclosing areas:
297347 * is_in(<lat>,<lng>)->.a
298348 *
299349 * and then return the union of the following sets:
300- *
301350 * relation(pivot.a)
302351 * way(pivot.a)
303352 *
304- * In both cases we then ask to retrieve tags and the geometry
305- * for each object.
353+ * In both cases we then ask to retrieve tags and the geometry for each object.
354+ *
355+ * TEMPORAL FILTERING (OpenHistoricalMap - Added Feature):
356+ * Filter objects to only include those that existed at the time slider date.
357+ * - Dates >= 1000 CE: Use OverpassQL filtering via `if:` condition
358+ * - Dates < 1000 CE: Use JavaScript filtering (OverpassQL limitation + BCE date comparison bug)
359+ * Filter logic: start_date <= currentDate AND (no end_date OR end_date > currentDate)
360+ *
361+ * Examples:
362+ * - currentDate="1914-07-28": start_date="1910", end_date="1920" → MATCHES
363+ * - currentDate="1914-07-28": start_date="1915" → EXCLUDED (started after)
364+ * - currentDate="-0003-01-01": start_date="-0003-06-01" → EXCLUDED (JavaScript handles BCE correctly)
365+ * - currentDate="-0003-01-01": start_date="-0004-01-01", end_date="-0002-01-01" → MATCHES
366+ *
367+ * https://wiki.openstreetmap.org/wiki/OpenHistoricalMap/Overpass#Country_boundaries_at_a_given_date_(start_of_WWI)
306368 */
307369 function queryOverpass ( latlng ) {
308370 const bounds = map . getBounds ( ) ,
@@ -312,10 +374,31 @@ OSM.Query = function (map) {
312374 . join ( ) ,
313375 geom = `geom(${ bbox } )` ,
314376 radius = 10 * Math . pow ( 1.5 , 19 - zoom ) ,
315- here = `(around:${ radius } ,${ latlng } )` ,
316- enclosed = "(pivot.a);out tags bb" ,
317- nearby = `(node${ here } ;way${ here } ;);out tags ${ geom } ;relation${ here } ;out ${ geom } ;` ,
318- isin = `is_in(${ latlng } )->.a;way${ enclosed } ;out ids ${ geom } ;relation${ enclosed } ;` ;
377+ here = `(around:${ radius } ,${ latlng } )` ;
378+
379+ let dateFilter = "" ; // OverpassQL filter (dates >= 1000 CE)
380+ let jsDateFilter = null ; // JavaScript filter (dates < 1000 CE)
381+
382+ if ( map . timeslider ) {
383+ const currentDate = map . timeslider . getDate ( ) ;
384+ if ( currentDate ) {
385+ if ( isBeforeYear1000 ( currentDate ) ) {
386+ // OverpassQL doesn't support dates < 1000 CE, filter in JavaScript instead
387+ jsDateFilter = currentDate ;
388+ } else {
389+ dateFilter = `(if: (!is_tag("start_date") || t["start_date"] <= "${ currentDate } ") && (!is_tag("end_date") || t["end_date"] > "${ currentDate } "))` ;
390+ }
391+ }
392+ }
393+
394+ // Build queries - match original format when no dateFilter
395+ const enclosed = dateFilter ? `(pivot.a)${ dateFilter } ` : "(pivot.a);out tags bb" ,
396+ nearby = dateFilter
397+ ? `(node${ here } ${ dateFilter } ;way${ here } ${ dateFilter } ;);out tags ${ geom } ;relation${ here } ${ dateFilter } ;out ${ geom } ;`
398+ : `(node${ here } ;way${ here } ;);out tags ${ geom } ;relation${ here } ;out ${ geom } ;` ,
399+ isin = dateFilter
400+ ? `is_in(${ latlng } )->.a;way${ enclosed } ;out geom;relation${ enclosed } ;out geom;`
401+ : `is_in(${ latlng } )->.a;way${ enclosed } ;out ids ${ geom } ;relation${ enclosed } ;` ;
319402
320403 $ ( "#sidebar_content .query-intro" )
321404 . hide ( ) ;
@@ -327,8 +410,8 @@ OSM.Query = function (map) {
327410 ...featureStyle
328411 } ) . addTo ( map ) ;
329412
330- runQuery ( nearby , $ ( "#query-nearby" ) , false ) ;
331- runQuery ( isin , $ ( "#query-isin" ) , true , ( feature1 , feature2 ) => size ( feature1 . bounds ) - size ( feature2 . bounds ) ) ;
413+ runQuery ( nearby , $ ( "#query-nearby" ) , false , null , jsDateFilter ) ;
414+ runQuery ( isin , $ ( "#query-isin" ) , true , ( feature1 , feature2 ) => size ( feature1 . bounds ) - size ( feature2 . bounds ) , jsDateFilter ) ;
332415 }
333416
334417 function clickHandler ( e ) {
@@ -361,7 +444,7 @@ OSM.Query = function (map) {
361444 page . load = function ( path , noCentre ) {
362445 // the original page.load content is the function below, and is used when one visits this page, be it first load OR later routing change
363446 // below, we wrap "if map.timeslider" so we only try to add the timeslider if we don't already have it
364- function originalLoadFunction ( ) {
447+ function originalLoadFunction ( ) {
365448 const params = new URLSearchParams ( path . substring ( path . indexOf ( "?" ) ) ) ,
366449 latlng = L . latLng ( params . get ( "lat" ) , params . get ( "lon" ) ) ;
367450
0 commit comments