[mapguide-commits] r10231 - in branches/4.0/MgDev: Server/src/UnitTesting UnitTest/TestData/RasterStylization
svn_mapguide at osgeo.org
svn_mapguide at osgeo.org
Fri Oct 23 18:07:41 PDT 2026
Author: jng
Date: 2026-10-23 18:07:41 -0700 (Fri, 23 Oct 2026)
New Revision: 10231
Added:
branches/4.0/MgDev/UnitTest/TestData/RasterStylization/RealRaster_truemarble32.tif
Modified:
branches/4.0/MgDev/Server/src/UnitTesting/TestRasterStylization.cpp
branches/4.0/MgDev/UnitTest/TestData/RasterStylization/PROVENANCE.md
Log:
#2174: Add failing test for striped raster rendering. Test raster from TrueMarble
Modified: branches/4.0/MgDev/Server/src/UnitTesting/TestRasterStylization.cpp
===================================================================
--- branches/4.0/MgDev/Server/src/UnitTesting/TestRasterStylization.cpp 2026-10-23 22:11:40 UTC (rev 10230)
+++ branches/4.0/MgDev/Server/src/UnitTesting/TestRasterStylization.cpp 2026-10-24 01:07:41 UTC (rev 10231)
@@ -460,6 +460,65 @@
return wkt;
}
+// RAII guard that raises the max raster image size for the lifetime of the
+// object and restores the previous values on destruction. The rendering service
+// reads the setting from configuration when it is constructed, so this must be
+// created before the service. The destructor restores the values even when a
+// REQUIRE aborts the test case early, so later cases keep their defaults.
+struct MaxRasterImageGuard
+{
+ MaxRasterImageGuard()
+ : m_oldWidth(MgConfigProperties::DefaultRenderingServicePropertyMaxRasterImageWidth),
+ m_oldHeight(MgConfigProperties::DefaultRenderingServicePropertyMaxRasterImageHeight)
+ {
+ MgConfiguration* config = MgConfiguration::GetInstance();
+ if (NULL != config)
+ {
+ config->GetIntValue(MgConfigProperties::RenderingServicePropertiesSection,
+ MgConfigProperties::RenderingServicePropertyMaxRasterImageWidth,
+ m_oldWidth, MgConfigProperties::DefaultRenderingServicePropertyMaxRasterImageWidth);
+ config->GetIntValue(MgConfigProperties::RenderingServicePropertiesSection,
+ MgConfigProperties::RenderingServicePropertyMaxRasterImageHeight,
+ m_oldHeight, MgConfigProperties::DefaultRenderingServicePropertyMaxRasterImageHeight);
+
+ Set(config, 8192, 8192);
+ }
+ }
+
+ ~MaxRasterImageGuard()
+ {
+ MgConfiguration* config = MgConfiguration::GetInstance();
+ if (NULL != config)
+ Set(config, m_oldWidth, m_oldHeight);
+ }
+
+ static void Set(MgConfiguration* config, INT32 width, INT32 height)
+ {
+ // MgConfiguration::SetProperties only accepts string properties: every
+ // entry in the ini file is stored as a string, and an Int32 property
+ // makes it throw MgInvalidPropertyTypeException.
+ STRING widthValue;
+ STRING heightValue;
+ MgUtil::Int32ToString(width, widthValue);
+ MgUtil::Int32ToString(height, heightValue);
+
+ Ptr<MgStringProperty> widthProp = new MgStringProperty();
+ widthProp->SetName(MgConfigProperties::RenderingServicePropertyMaxRasterImageWidth);
+ widthProp->SetValue(widthValue);
+ Ptr<MgStringProperty> heightProp = new MgStringProperty();
+ heightProp->SetName(MgConfigProperties::RenderingServicePropertyMaxRasterImageHeight);
+ heightProp->SetValue(heightValue);
+
+ Ptr<MgPropertyCollection> props = new MgPropertyCollection();
+ props->Add(widthProp);
+ props->Add(heightProp);
+ config->SetProperties(MgConfigProperties::RenderingServicePropertiesSection, props);
+ }
+
+ INT32 m_oldWidth;
+ INT32 m_oldHeight;
+};
+
}
TEST_CASE("Raster grid styling", "[RasterStylization]")
@@ -654,3 +713,139 @@
MgFdoConnectionManager::GetInstance()->ClearCache();
}
}
+
+TEST_CASE("TrueMarble striped rendering", "[RasterStylization]")
+{
+ // Reproducer for the GDAL provider "striped" rendering defect, using a
+ // single checked-in tile of the free "Global True Marble" dataset (see
+ // UnitTest/TestData/RasterStylization/PROVENANCE.md). The tile itself is a
+ // 4.5 MB GeoTIFF; the full dataset is ~70 GB, which is why only this one
+ // tile is checked in.
+ //
+ // It requests the small 32 km overview tile (1350x675, 3 x Byte) at
+ // 4000x2000, an upscale that crosses the provider's hard-coded 4 MP
+ // scanline-tiling threshold (FdoRfpBandRaster::GetDataModel). Above that
+ // threshold the provider reads one scanline per tile, and the per-row
+ // source window in FdoRfpStreamReaderByTile::_getTile rounds to zero
+ // height for some rows when upsampling. Those rows are never overwritten,
+ // so they keep the tile's 0xFF prefill and survive the pipeline as whole
+ // rows of pure white: the stripes.
+ //
+ // The defect is in the provider's output stream, so it can be detected
+ // objectively without a human looking at the render: real imagery never
+ // contains a full-width, exactly-white row. For this 4000x2000 upscale the
+ // per-row source window rounds to zero height for a large fraction of the
+ // output rows (653 of 2000 in the actual Release run), and GDAL treats a
+ // zero-height window as a no-op, leaving the 0xFF prefill. The PNG is
+ // still written to Results/ for visual confirmation.
+
+ // Raise the max raster image size so the 4000x2000 request is not halved by
+ // RasterAdapter before it reaches the provider (the halving loop halves any
+ // image >= MaxRasterImageWidth/Height). Each rendering service reads this
+ // from configuration when it is constructed, so the guard is created before
+ // the service and restores the values when it goes out of scope.
+ MaxRasterImageGuard rasterGuard;
+
+ Ptr<MgServerSiteService> svcSite = TestServiceFactory::CreateSiteService();
+ Ptr<MgSiteConnection> siteConn = TestServiceFactory::CreateSiteConnection(svcSite);
+ Ptr<MgResourceService> svcResource = TestServiceFactory::CreateResourceService();
+ Ptr<MgRenderingService> svcRendering = TestServiceFactory::CreateRenderingService();
+ Ptr<MgFeatureService> svcFeature = dynamic_cast<MgFeatureService*>(
+ MgServiceManager::GetInstance()->RequestService(MgServiceType::FeatureService));
+
+ const double dpi = 96.0;
+ const double metersPerInch = 0.0254;
+ const int renderWidth = 4000;
+ const int renderHeight = 2000;
+
+ const STRING name = L"TrueMarble_striped";
+ STRING fsId = L"Library://RasterStylizationReal/Data/" + name + L".FeatureSource";
+ STRING layerId = L"Library://RasterStylizationReal/Layers/" + name + L".LayerDefinition";
+ STRING mapId = L"Library://RasterStylizationReal/Maps/" + name + L".MapDefinition";
+
+ STRING rasterProperty;
+ STRING featureName = PublishFeatureSource(svcResource, svcFeature, fsId,
+ Narrow(FixturePath("RealRaster_truemarble32.tif")),
+ rasterProperty);
+
+ // The raster's own footprint drives the map view, so the image shows the
+ // whole file (stretched to the 4000x2000 request, which is the upscale that
+ // triggers the defect).
+ Ptr<MgEnvelope> rasterExtent;
+ INT32 imageXSize = 0;
+ INT32 imageYSize = 0;
+ REQUIRE(GetRasterFootprint(svcFeature, fsId, featureName, rasterProperty,
+ rasterExtent, imageXSize, imageYSize));
+
+ double minX = rasterExtent->GetLowerLeftCoordinate()->GetX();
+ double minY = rasterExtent->GetLowerLeftCoordinate()->GetY();
+ double maxX = rasterExtent->GetUpperRightCoordinate()->GetX();
+ double maxY = rasterExtent->GetUpperRightCoordinate()->GetY();
+
+ STRING csWkt = GetRasterCoordinateSystem(svcFeature, fsId);
+ if (csWkt.empty())
+ csWkt = LL84_WKT;
+
+ // The 3 x Byte tile reads through GridData::ReadRaster's 24 bpp branch,
+ // which packs the RGB samples into one ARGB value, so the plain <Band>
+ // colour style passes it through unchanged.
+ PublishResource(svcResource, layerId, LayerXml(fsId, featureName, rasterProperty, StyleBand));
+ PublishResource(svcResource, mapId, MapXml(layerId, csWkt, minX, minY, maxX, maxY));
+
+ Ptr<MgResourceIdentifier> mapRes = new MgResourceIdentifier(mapId);
+ Ptr<MgMap> map = new MgMap(siteConn);
+ map->Create(mapRes, L"RasterStylizationMap");
+
+ Ptr<MgCoordinate> centre = new MgCoordinateXY((minX + maxX) / 2.0, (minY + maxY) / 2.0);
+ Ptr<MgPoint> centrePoint = new MgPoint(centre);
+ map->SetViewCenter(centrePoint);
+ map->SetDisplayDpi((INT32)dpi);
+ map->SetDisplayWidth(renderWidth);
+ map->SetDisplayHeight(renderHeight);
+ // One image pixel spans (maxX - minX) / renderWidth map units, so the
+ // requested raster size comes out at renderWidth x renderHeight.
+ map->SetViewScale(((maxX - minX) / renderWidth) * map->GetMetersPerUnit() * dpi / metersPerInch);
+
+ int width = 0;
+ int height = 0;
+ unsigned int* rawPixels = RenderPng(svcRendering, map, width, height, name + L".png");
+ REQUIRE(rawPixels != NULL);
+ // DecodePNG hands back a buffer that has to be released with delete[].
+ std::unique_ptr<unsigned char[]> pixels(reinterpret_cast<unsigned char*>(rawPixels));
+ REQUIRE(width == renderWidth);
+ REQUIRE(height == renderHeight);
+
+ // Count rows that are exactly opaque white. A striped row is the provider's
+ // 0xFF prefill passed through the 24 bpp branch, which packs it to
+ // 0xFFFFFFFF. DecodePNG returns the image bottom up, which does not matter
+ // for a per-row count.
+ int whiteRows = 0;
+ for (int y = 0; y < height; ++y)
+ {
+ bool allWhite = true;
+ for (int x = 0; x < width; ++x)
+ {
+ if ((rawPixels[y * width + x] & 0x00FFFFFF) != 0xFFFFFF)
+ {
+ allWhite = false;
+ break;
+ }
+ }
+ if (allWhite)
+ ++whiteRows;
+ }
+
+ ACE_DEBUG((LM_INFO, ACE_TEXT(" wrote %W (%dx%d from %dx%d), %d uniformly white rows\n"),
+ ResultPath(name + L".png").c_str(), width, height, imageXSize, imageYSize, whiteRows));
+
+ INFO("rendered " << width << "x" << height << " with " << whiteRows
+ << " uniformly white rows out of " << height);
+ // Real imagery never contains a full-width, exactly-white row. Allow a tiny
+ // tolerance for safety; the defect produces on the order of a third of the
+ // image height in white rows, so this fails hard while the bug is present.
+ CHECK(whiteRows <= height / 100);
+
+ // Release the pooled raster connection now, for the same reason as the
+ // other cases in this suite.
+ MgFdoConnectionManager::GetInstance()->ClearCache();
+}
Modified: branches/4.0/MgDev/UnitTest/TestData/RasterStylization/PROVENANCE.md
===================================================================
--- branches/4.0/MgDev/UnitTest/TestData/RasterStylization/PROVENANCE.md 2026-10-23 22:11:40 UTC (rev 10230)
+++ branches/4.0/MgDev/UnitTest/TestData/RasterStylization/PROVENANCE.md 2026-10-24 01:07:41 UTC (rev 10231)
@@ -325,6 +325,30 @@
5. Record the upstream path, hash, size, derivation command and what the file is
in the tables above.
+## The True Marble tile (`RealRaster_truemarble32.tif`)
+
+This one is not derived from the FDO provider test data. It is a single tile of
+"Global True Marble", the free reduced-resolution release of Unearthed
+Outdoors' True Marble global colour imagery. It drives the `[RasterStylization]`
+"TrueMarble striped rendering" case, which reproduces the GDAL provider's
+striped upsampling defect: that case renders the 1350x675 tile at 4000x2000, an
+upscale that crosses the provider's 4 MP scanline-tiling threshold and leaves
+whole rows as the provider's 0xFF prefill.
+
+| Property | Value |
+|----------|-------|
+| Origin | Unearthed Outdoors, LLC - "Global True Marble", 32 km resolution global tile |
+| Source file | `TrueMarble.32km.1350x675.tif` from the free Global True Marble download |
+| Licence | Creative Commons Attribution 3.0 United States (`License.html` in the download) |
+| Attribution | "We only ask that copyright be attributed to Unearthed Outdoors when reproduced." |
+| Size | 1350x675 px, 4,719,372 bytes, 3 x Byte (RGB), WGS84 geographic |
+| Reprocessing | None - checked in byte for byte as downloaded |
+
+The requested render width is 4000 pixels, so `width * bitsPerPixel` is
+`4000 * 24 = 96000`, a multiple of 64, and the tile still reaches the
+`GridData::ReadRaster` 24 bpp branch this suite exists to cover - the 64-bit
+alignment constraint is on the *requested* width, not the file's native width.
+
## File inventory
| File | Bytes | SHA-256 |
@@ -333,6 +357,7 @@
| `RealRaster_cm13ct.tif` | 2,287,461 | `998430170a82cabf8cad85aa0cf876e7765f35fa01fb4d7f6e252612c5f1fb0f` |
| `RealRaster_mtjefferson.tif` | 315,773 | `e2b7016d355f3cc17e586cd4c834e63dfe0008253443f8eb54330c4339606f1f` |
| `RealRaster_spif83.tif` | 262,871 | `49e48c375e721bea9a72d19cfec2a48641b83aa6951b97393d8a4ad992394310` |
+| `RealRaster_truemarble32.tif` | 4,719,372 | `dd9de7a8486350c795b81a350f202c649c4b40727af1a3cd391f93b33af1c72f` |
The derivation commands use GDAL 3.12.4 (the toolchain in Part 1); a different
GDAL version produces a different (still lossless) encoding and therefore a
Added: branches/4.0/MgDev/UnitTest/TestData/RasterStylization/RealRaster_truemarble32.tif
===================================================================
(Binary files differ)
Index: branches/4.0/MgDev/UnitTest/TestData/RasterStylization/RealRaster_truemarble32.tif
===================================================================
--- branches/4.0/MgDev/UnitTest/TestData/RasterStylization/RealRaster_truemarble32.tif 2026-10-23 22:11:40 UTC (rev 10230)
+++ branches/4.0/MgDev/UnitTest/TestData/RasterStylization/RealRaster_truemarble32.tif 2026-10-24 01:07:41 UTC (rev 10231)
Property changes on: branches/4.0/MgDev/UnitTest/TestData/RasterStylization/RealRaster_truemarble32.tif
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+image/tiff
\ No newline at end of property
More information about the mapguide-commits
mailing list