How do I use negative regex in the style section of an Overpass Turbo query?

I’m trying my hand at customizing the styles of the output of an Overpass Turbo query by following the MapCSS specification.

I’m trying to show all restaurants that are nodes or ways within the bounding box. Furthermore, I want restaurants that have never been checked or haven’t been checked in 2024 or 2025 to be red.

My query is as follows (link to the same query in Overpass Turbo):

[timeout:25];
nw[amenity=restaurant]({{bbox}});
out geom;
{{style:
way[check_date!~/^2024.*$/],
node[check_date!~/^2024.*$/],
way[check_date!~/^2025.*$/],
node[check_date!~/^2024.*$/],
way[!check_date],
node[!check_date]
{ color:red; width:1; }
}}

Expected result: Shows me all restaurants with those that haven’t been checked since prior to 2024 or never checked at all painted red.

Actual result: “Invalid MapCSS stylesheet: MapCSS runtime error.”

Additional info:

The following query applies my desired styles to restaurants that have never been checked (Overpass Turbo):

[timeout:25];
nw[amenity=restaurant]({{bbox}});
out geom;
{{style:
way[!check_date],
node[!check_date]
{ color:red; width:1; }
}}

And the following query applies my desired styles to restaurants that have been checked in 2024 or 2025 (Overpass Turbo):

[timeout:25];
nw[amenity=restaurant]({{bbox}});
out geom;
{{style:
way[check_date=~/^2024.*$/],
node[check_date=~/^2024.*$/],
way[check_date=~/^2025.*$/],
node[check_date=~/^2024.*$/]
{ color:red; width:1; }
}}

But how do I negate these conditions (which is what I really want)?

Following the examples on JOSM/MapCSS Validator Syntax, I think this should work (reference the last example in the table under the section “Value evaluation within blocks”); however, these examples are from the MapCSS page on the JOSM wiki. I’ve found two MapCSS pages on the OSM wiki that do not mention the specific use case I’m describing above: Overpass turbo/MapCSS and Overpass API/Overpass API by Example.

Am I doomed? Thanks in advance!

My instinct was to adjust the query to only return elements fitting your criteria, rather than attempting to accomplish that with the style.

Unfortunately, this doesn’t also render the restaurants not in need of checking, so it’s not a full solution:

https://overpass-turbo.eu/s/1XQl

[out:xml][timeout:300];

// Get all nodes and ways in view which are tagged with "amenity=restaurant" and store them in the set allRestaurants
nw({{bbox}})["amenity"="restaurant"]->.allRestaurants;

// Get all nodes and ways in the set allRestaurants which are tagged with a "check_date" value containing "2024" or "2025" and store them in the set recentlyChecked
nw.allRestaurants["check_date"~"2024|2025"]->.recentlyChecked;

// Subtract all nodes and ways in the set recentlyChecked from all nodes and ways in the set allRestaurants
(nw.allRestaurants; - nw.recentlyChecked;);

// Output: all nodes and ways in view which are tagged with "amenity=restaurant" and not tagged with a "check_date" value containing "2024" or "2025"
(._;>;); out meta;

// Style all objects as desired
{{style:
*{ color: red; fill-color: red; width: 1; }
}}

I was hoping for set-based selection, as this comment from 2017 on the Wiki Discussion page echoes, but I couldn’t find a way to make that work.

You might have better fortune with Ultra and its MapLibre styling.

1 Like

The way to negate with the style is to first apply a default style.

In your style section, first appy a default style selection for all elements (here colored with a larger red line) . Your specific mapcss style selections below for the objects with check_date will override the default style.

/* default style */
node, way{ color:red; width:4; }
1 Like

Thanks for chiming in—this is the conclusion I came to as well, and this approach meets my needs wonderfully. Here’s the latest version of my query. Basically, it’s a heat map of which POIs haven’t been checked within various time frames, if ever. I studied the example on Overpass API/Overpass API by Example, “Highlight Objects with a check_date older than X” section.

/*
This Overpass query produces a heat map of businesses based on check_date.
*/
[timeout:25];
(
nwr[access!=private][amenity~"^restaurant|fast_food|bar|ice_cream|cafe|theatre|bank|veterinary|clinic|biergarten|library|fuel|doctors|pharmacy|dentist$"]({{bbox}});
nwr[access!=private][amenity="pub"]({{bbox}});
nwr[access!=private][shop]({{bbox}});
nwr[access!=private][leisure="fitness_centre"]({{bbox}});
)->.all;
nwr.all;
out geom;

node.all[check_date];
convert node
  ::id=id(), 
  checked30=date(t["check_date"])>date("{{date:30days}}"),
  checked365=date(t["check_date"])>date("{{date:365days}}"),
  checked2y=date(t["check_date"])>date("{{date:2years}}"),
  checked3y=date(t["check_date"])>date("{{date:3years}}"),
  neverchecked=0
;
out;

way.all[check_date];
convert way
  ::id=id(),
  checked30=date(t["check_date"])>date("{{date:30days}}"),
  checked365=date(t["check_date"])>date("{{date:365days}}"),
  checked2y=date(t["check_date"])>date("{{date:2years}}"),
  checked3y=date(t["check_date"])>date("{{date:3years}}"),
  neverchecked=0
;
out;


rel.all[check_date];
convert relation
  ::id=id(),
  checked30=date(t["check_date"])>date("{{date:30days}}"),
  checked365=date(t["check_date"])>date("{{date:365days}}"),
  checked2y=date(t["check_date"])>date("{{date:2years}}"),
  checked3y=date(t["check_date"])>date("{{date:3years}}"),
  neverchecked=0
;
out;

node.all[!check_date];
convert node
  ::id=id(), 
  checked30=0,                                     
  checked365=0,
  checked2y=0,
  checked3y=0,
  neverchecked=1
;
out;

way.all[!check_date];
convert way
  ::id=id(),
  checked30=0,
  checked365=0,
  checked2y=0,
  checked3y=0,
  neverchecked=1
;
out;


rel.all[!check_date];
convert relation
  ::id=id(), 
  checked30=0,                                     
  checked365=0,
  checked2y=0,
  checked3y=0,
  neverchecked=1
;           
out;
                                     
{{style:
way, node, relation way { color:#d73027; width:3; }
way[checked3y=1], node[checked3y=1], relation[checked3y=1] way { color:#fdae61; width:3; }
way[checked2y=1], node[checked2y=1], relation[checked2y=1] way { color:#fee08b; width:3; }
way[checked365=1], node[checked365=1], relation[checked365=1] way { color:#ffffbf; width:3; }
way[checked30=1], node[checked30=1], relation[checked30=1] way { color:#66bd63; width:3; }
}}
1 Like

Clever - thank you for sharing!