Repeating image pattern?

Is it possible to create an image pattern that will simply repeat as necessary, without changing the size of the image being repeated?

For example, I have a rectangle (or any polygon, polyline, or ellipse), and I want to fill it with a custom SVG that I've created, but I don't want to specify the number of times to repeat, I just want it to keep the image the exact size it is, but repeat it as many times as can fit. As I zoom in and out of the map, I need it to fill in more or less of my image to fill my shape.

The reason I need this is because I'm creating military overlays, and I've created the various fill images required, but they look horrible on my map because when I'm zoomed out, the images become so small you can hardly see them and can't tell what they are, and when you're zoomed in far enough, you can't see them at all because you're "inside" the fill image.

I believe "stipple" is probably the correct word to describe what I'm trying to do.

Maybe make it an ideal size when the camera is very close, then use minimumPixelSize to make sure it doesn’t become too small from afar. Like the model of the airplane on this SandCastle app

Assuming that applies to other objects as well and not just models.

Looks like that option is only available for models. One other problem would be that my shapes are given to me dynamically by a 3rd party system (users can create their own shapes with fills and line styles, and that data gets passed to me), so I don't know ahead of time how many times to make it repeat for it to look right.

Does anyone know if making a repeating pattern on a canvas and then using that canvas as the fill image would work? I am trying to get that working (I can make an image repeat on a canvas exactly how I want it to), but I can't seem to make an image property take my canvas so I can't yet tell if that would work.

You can call canvas.toDataURL() and assign that to the material. But I don’t think that will give you what you want, since you want the number of repetitions to change based on view distance and size of the geometry, correct?

I believe this is possible in Cesium with a custom material, and my gut tells me it wouldn’t be much code; but unfortunately this isn’t my area of expertise. I’ll ask around.

Thank you so much Matthew, any help you can find for me is greatly appreciated - this feature is required for the project I'm working on and it is going to be fielded so lots of people will see it (they've seen some beta versions that didn't yet have these shapes, and they're loving Cesium).

That's correct, I need the number of repetitions to change based on the size of the object, so that the fill image always remains the same size, it just adds more if the shape is bigger, and fewer if it's smaller.

Pretty much what this HTML5 canvas tutorial describes:

It lets you set an image to be repeated in the canvas as many times as possible without changing the size of the image (so the larger you make the canvas, the more images it adds to fill it in, and likewise the smaller the canvas gets, the fewer images are needed to fill it, still maintaining their original size). I was hoping that perhaps if I create a canvas this way, setting the repeat options and all that, then if I apply it to the polygon or polyline fill image property without any repetitions declared, maybe it would work as the fill area gets larger and smaller when zooming in and out. It was a long shot and likely not going to work :slight_smile:


It’s your lucky day. This peaked my curiosity and so I managed to talk Dan into giving me some code to get me started; then I wrapped that in the Entity API. The result is pretty awesome and turned out way better than I could have imagined. Here’s a link to the code, just copy and paste it into Sandcastle: There are comments with instructions, but if you have a hard time integrating it into your app, just let me know. This worked out so well that I’ll probably look into making it an official part of Cesium for 1.8.


Thank you soooo much Matt (and Dan)!!!!! You guys are my heroes :slight_smile:

That demo looks amazing in Sandcastle, I will try it tomorrow with my custom SVGs and post how it goes.

It’s kind of strange having the texture always face the camera and not rotate, but I suppose for the application this is actually beneficial for ease of identification.

Ok I tried this out with my own SVG, and for a polygon fill it is perfect, thank you!!! The only issue I'm having is figuring out how to make a polyline repeat my image along the length of the line only once. When I apply this wallpaper to the polyline, it does fill it like the polygon, but it basically makes it look like a very "short" polygon, and the shape gets cut off vertically as it repeats. What I need it to do is simply repeat my image along the length of the line. For example, I'm using a small circle as my shape for testing, and I'd like it to just repeat that circle over and over again along the length of the line, almost like a series of points along the line. Is that possible to do? I will create a sandcastle example to paste in so you can see what I mean, but I figured I'd throw it out there in case you already know how to do what I'm trying to make the polyline do.

Sorry to be the bearer of bad news, but polylines are a whole different beast and a lot more complicated. I would like to get to supporting something like this for them eventually, but it’s probably a long way off.

Just my luck :slight_smile: Thanks for the heads up, I'll have to figure out some interim solution then until it's part of the API since making funky lines is required for military overlays. I'll try and do some research into webGL to see if I can get something that's at least passable (doesn't have to be perfect at the intersections, etc). Let me know if you have any suggestions or pointers - I'll take it even if it's ugly.

Unfortunately it’s a much harder problem (which is why we haven’t done it yet). The shaders for PolylineArrowMaterial and PolylineOutline may be of some help if you wanted to start experimenting with this (and we’d be happy to take a PR for it if you get it working). You might be able to take the slope of the line into account when calculating the new image position. Other than that, I can’t really provide any guidance, sorry.

Hey Matt, I'm sure I'm doing something stupid here, but I can't seem to get the wallpaper to work across different data sources when clicking a button. The first wallpaper instance will load with no problem, but the second one has an empty fill. Note that if I use viewer.entities for both, it works fine. Also note that if I create both entities right after one another, without the button click involved, it also works fine.

Here is a sandcastle example, it's mostly what you already gave me but with 2 buttons added to illustrate the issue. Click the buttons in any order, the first click will work but the second will fail. Perhaps I'm using CustomDataSource incorrectly?

This goes into the HTML/CSS tab:

    @import url(../templates/bucket.css);
<div id="cesiumContainer" class="fullSize"></div>
<div id="loadingOverlay"><h1>Loading...</h1></div>
<div id="test1"></div>
<div id="test2"></div>
<div id="toolbar"></div>

This goes into the Javascript tab:

//Call this once at application startup
Cesium.Material._materialCache.addMaterial('Wallpaper', {
fabric : {
type : 'Wallpaper',
uniforms : {
image : Cesium.Material.DefaultImageId,
anchor : new Cesium.Cartesian2(0, 0)
components : {
diffuse : 'texture2D(image, fract((gl_FragCoord.xy - anchor.xy) / vec2(imageDimensions.xy))).rgb',
alpha : 'texture2D(image, fract((gl_FragCoord.xy - anchor.xy) / vec2(imageDimensions.xy))).a'
translucent : false

//Create an instance and assign to anything that has a material property.
//scene - the scene
//image - the image (I think both a url or Image object are supported)
//anchor - A Cartesian3 that is the most southern and westard point of the geometry
var WallPaperMaterialProperty = function(scene, image, anchor) {
this._scene = scene;
this._image = image;
this._anchor = anchor;
this.definitionChanged = new Cesium.Event();
this.isConstant = true;

WallPaperMaterialProperty.prototype.getType = function(time) {
return 'Wallpaper';

WallPaperMaterialProperty.prototype.getValue = function(time, result) {
if (!Cesium.defined(result)) {
result = {
image : undefined,
anchor : undefined
result.image = this._image;
result.anchor = Cesium.SceneTransforms.wgs84ToDrawingBufferCoordinates(this._scene, this._anchor, result.anchor);
result.anchor.x = Math.floor(result.anchor.x);
result.anchor.y = Math.floor(result.anchor.y);
} else {
result.anchor = new Cesium.Cartesian2(0, 0);
return result;

WallPaperMaterialProperty.prototype.equals = function(other) {
return this === other || //
(other instanceof WallPaperMaterialProperty && //
this._image === other._image);

//Here's a working example.
var viewer = new Cesium.Viewer('cesiumContainer');

Sandcastle.addToolbarButton('Test1', function() {
    var customDataSource1 = new Cesium.CustomDataSource("testing");
    var customRectangle = customDataSource1.entities.add({
      id : "rect1",
      rectangle : {
        coordinates : Cesium.Rectangle.fromDegrees(-119, 44, -112, 47),
        material : new WallPaperMaterialProperty(viewer.scene, "../images/checkerboard.png", Cesium.Cartesian3.fromDegrees(-119, 44)),
        outline : true,
        outlineColor : Cesium.Color.RED
}, 'test1');

Sandcastle.addToolbarButton('Test2', function() {
    var customRectangle2 = viewer.entities.add({
      id : "rect2",
      rectangle : {
        coordinates : Cesium.Rectangle.fromDegrees(-110.0, 20.0, -80.0, 25.0),
        material : new WallPaperMaterialProperty(viewer.scene, "../images/checkerboard.png", Cesium.Cartesian3.fromDegrees(-110, 20)),
        outline : true,
        outlineColor : Cesium.Color.RED
}, 'test2');


I’m not quite sure what the problem is here. It only seems to be an issue with the custom material and doesn’t happen with the others (though as far as I can tell there’s nothing special going on.). I’d say there’s a small chance this is a bug on the Cesium end, but I need to debug some more to know for sure.

Okay, this was definitely a Cesium bug, I opened with a fix:

Thanks for reporting this, Di.