Index: trunk/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/style/Layers.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/style/Layers.java	(revision 18576)
+++ trunk/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/style/Layers.java	(revision 18578)
@@ -76,5 +76,5 @@
 
     /** Default paint properties for this layer */
-    private final String paint;
+    private final String paintProperties;
 
     /** A source description to be used with this layer. Required for everything <i>but</i> {@link Type#BACKGROUND} */
@@ -106,6 +106,12 @@
             this.filter = Expression.EMPTY_EXPRESSION;
         }
-        this.maxZoom = layerInfo.getInt("maxzoom", Integer.MAX_VALUE);
+        // minZoom <= showable zooms < maxZoom. This should be fractional, but our mapcss implementations expects ints.
         this.minZoom = layerInfo.getInt("minzoom", Integer.MIN_VALUE);
+        int tMaxZoom = layerInfo.getInt("maxzoom", Integer.MAX_VALUE);
+        if (tMaxZoom == Integer.MAX_VALUE) {
+            this.maxZoom = Integer.MAX_VALUE;
+        } else {
+            this.maxZoom = Math.max(this.minZoom, Math.max(0, tMaxZoom - 1));
+        }
         // There is a metadata field (I don't *think* I need it?)
         // source is only optional with {@link Type#BACKGROUND}.
@@ -123,30 +129,30 @@
                 case FILL:
                     // area
-                    this.paint = parsePaintFill(paintObject);
+                    this.paintProperties = parsePaintFill(paintObject);
                     break;
                 case LINE:
                     // way
-                    this.paint = parsePaintLine(layoutObject, paintObject);
+                    this.paintProperties = parsePaintLine(layoutObject, paintObject);
                     break;
                 case CIRCLE:
                     // point
-                    this.paint = parsePaintCircle(paintObject);
+                    this.paintProperties = parsePaintCircle(paintObject);
                     break;
                 case SYMBOL:
                     // point
-                    this.paint = parsePaintSymbol(layoutObject, paintObject);
+                    this.paintProperties = parsePaintSymbol(layoutObject, paintObject);
                     break;
                 case BACKGROUND:
                     // canvas only
-                    this.paint = parsePaintBackground(paintObject);
+                    this.paintProperties = parsePaintBackground(paintObject);
                     break;
                 default:
-                    this.paint = EMPTY_STRING;
+                    this.paintProperties = EMPTY_STRING;
                 }
             } else {
-                this.paint = EMPTY_STRING;
+                this.paintProperties = EMPTY_STRING;
             }
         } else {
-            this.paint = EMPTY_STRING;
+            this.paintProperties = EMPTY_STRING;
         }
         this.sourceLayer = layerInfo.getString("source-layer", null);
@@ -454,9 +460,9 @@
     @Override
     public String toString() {
-        if (this.filter.toString().isEmpty() && this.paint.isEmpty()) {
+        if (this.filter.toString().isEmpty() && this.paintProperties.isEmpty()) {
             return EMPTY_STRING;
         } else if (this.type == Type.BACKGROUND) {
             // AFAIK, paint has no zoom levels, and doesn't accept a layer
-            return "canvas{" + this.paint + "}";
+            return "canvas{" + this.paintProperties + "}";
         }
 
@@ -473,5 +479,5 @@
             zoomSelector = EMPTY_STRING;
         }
-        final String commonData = zoomSelector + this.filter.toString() + "::" + this.id + "{" + this.paint + "}";
+        final String commonData = zoomSelector + this.filter.toString() + "::" + this.id + "{" + this.paintProperties + "}";
 
         if (this.type == Type.CIRCLE || this.type == Type.SYMBOL) {
@@ -513,5 +519,5 @@
               && Objects.equals(this.source, o.source)
               && Objects.equals(this.filter, o.filter)
-              && Objects.equals(this.paint, o.paint);
+              && Objects.equals(this.paintProperties, o.paintProperties);
         }
         return false;
@@ -521,5 +527,5 @@
     public int hashCode() {
         return Objects.hash(this.type, this.minZoom, this.maxZoom, this.id, this.styleId, this.sourceLayer, this.source,
-          this.filter, this.paint);
+          this.filter, this.paintProperties);
     }
 }
Index: trunk/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/style/package-info.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/style/package-info.java	(revision 18578)
+++ trunk/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/style/package-info.java	(revision 18578)
@@ -0,0 +1,8 @@
+// License: GPL. For details, see LICENSE file.
+
+/**
+ * Provides classes for converting Mapbox Vector Styles to JOSM MapCSS stylesheets
+ * See the <a href="https://docs.mapbox.com/mapbox-gl-js/style-spec/">style specification</a> for
+ * more information on what the Mapbox Vector Style translation code expects.
+ */
+package org.openstreetmap.josm.data.imagery.vectortile.mapbox.style;
Index: trunk/src/org/openstreetmap/josm/data/osm/PrimitiveId.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/PrimitiveId.java	(revision 18576)
+++ trunk/src/org/openstreetmap/josm/data/osm/PrimitiveId.java	(revision 18578)
@@ -17,5 +17,7 @@
 
     /**
-     * Gets the type of object represented by this object.
+     * Gets the type of object represented by this object. Note that this should
+     * return the base primitive type ({@link OsmPrimitiveType#NODE},
+     * {@link OsmPrimitiveType#WAY}, and {@link OsmPrimitiveType#RELATION}).
      *
      * @return the object type
Index: trunk/src/org/openstreetmap/josm/data/vector/VectorDataStore.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/vector/VectorDataStore.java	(revision 18576)
+++ trunk/src/org/openstreetmap/josm/data/vector/VectorDataStore.java	(revision 18578)
@@ -78,10 +78,10 @@
             if (mergedRelation.getMemberPrimitivesList().stream().allMatch(IWay.class::isInstance)) {
                 // This pretty much does the "right" thing
-                this.mergeWays(mergedRelation);
+                mergeWays(mergedRelation);
             } else if (!(primitive instanceof IWay)) {
                 // Can't merge, ever (one of the childs is a node/relation)
                 mergedRelation.remove(JOSM_MERGE_TYPE_KEY);
             }
-        } else if (mergedRelation != null && primitive instanceof IRelation) {
+        } else if (mergedRelation != null && primitive instanceof VectorRelation) {
             // Just add to the relation
             ((VectorRelation) primitive).getMembers().forEach(mergedRelation::addRelationMember);
@@ -99,5 +99,5 @@
     }
 
-    private VectorPrimitive mergeWays(VectorRelation relation) {
+    private static VectorPrimitive mergeWays(VectorRelation relation) {
         List<VectorRelationMember> members = RelationSorter.sortMembersByConnectivity(relation.getMembers());
         Collection<VectorWay> relationWayList = members.stream().map(VectorRelationMember::getMember)
@@ -268,7 +268,16 @@
       Collection<VectorPrimitive> featureObjects, Area area) {
         VectorRelation vectorRelation = new VectorRelation(layer.getName());
-        for (VectorPrimitive member : pathIteratorToObjects(tile, layer, featureObjects, area.getPathIterator(null))) {
+        PathIterator pathIterator = area.getPathIterator(null);
+        int windingRule = pathIterator.getWindingRule();
+        for (VectorPrimitive member : pathIteratorToObjects(tile, layer, featureObjects, pathIterator)) {
             final String role;
             if (member instanceof VectorWay && ((VectorWay) member).isClosed()) {
+                // Area messes up the winding. See #22404.
+                if (windingRule == PathIterator.WIND_NON_ZERO) {
+                    VectorWay vectorWay = (VectorWay) member;
+                    List<VectorNode> nodes = new ArrayList<>(vectorWay.getNodes());
+                    Collections.reverse(nodes);
+                    vectorWay.setNodes(nodes);
+                }
                 role = Geometry.isClockwise(((VectorWay) member).getNodes()) ? "outer" : "inner";
             } else {
@@ -375,6 +384,11 @@
             primitive = pathToWay(tile, layer, featureObjects, (Path2D) shape).stream().findFirst().orElse(null);
         } else if (shape instanceof Area) {
-            primitive = areaToRelation(tile, layer, featureObjects, (Area) shape);
-            primitive.put(RELATION_TYPE, MULTIPOLYGON_TYPE);
+            VectorRelation vectorRelation = areaToRelation(tile, layer, featureObjects, (Area) shape);
+            if (vectorRelation.getMembersCount() != 1) {
+                primitive = vectorRelation;
+                primitive.put(RELATION_TYPE, MULTIPOLYGON_TYPE);
+            } else {
+                primitive = vectorRelation.getMember(0).getMember();
+            }
         } else {
             // We shouldn't hit this, but just in case
Index: trunk/src/org/openstreetmap/josm/data/vector/VectorWay.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/vector/VectorWay.java	(revision 18576)
+++ trunk/src/org/openstreetmap/josm/data/vector/VectorWay.java	(revision 18578)
@@ -131,4 +131,9 @@
     @Override
     public OsmPrimitiveType getType() {
+        return OsmPrimitiveType.WAY;
+    }
+
+    @Override
+    public OsmPrimitiveType getDisplayType() {
         return this.isClosed() ? OsmPrimitiveType.CLOSEDWAY : OsmPrimitiveType.WAY;
     }
Index: trunk/src/org/openstreetmap/josm/data/vector/package-info.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/vector/package-info.java	(revision 18578)
+++ trunk/src/org/openstreetmap/josm/data/vector/package-info.java	(revision 18578)
@@ -0,0 +1,6 @@
+// License: GPL. For details, see LICENSE file.
+
+/**
+ * Provides classes for vector data like Mapbox Vector Tiles.
+ */
+package org.openstreetmap.josm.data.vector;
