Ok, this answer demonstrates the graphic update to draw and move the grid and also an attempt to align the grid using the w3w API.
So one issue with using the w3w seems to be how to compute the location of a
grid cell. Since the algorithm evidently is private, for this implementation
the 'grid' rest api call is used for the current screen center point (on idle) and the json response parsed for a candidate reference point.
In this example a polygon is always drawn for the "reference" grid cell
obtained from the w3w grid call.
The grid view implementation uses the canvas.translate call to properly align
and draw the grid using an offset computed from the reference point.
This works at any latitude due to the use of the SphericalUtil usage for mapping distance to screen pixels.
Recording at bottom (low quality).
Main Activity
Here the w3w grid rest call is made on camera idle and zoom far enough out (there's no need to keep realigning in close zoom) and the result (a corner point of a nearby grid cell to be used as a
reference point) is fed to the grid view. A filled polygon is drawn to denote
the reference grid cell.
On camera movement, the same reference point is used but the current screen position is used to maintain the proper offsets.
public void what3words() {
// some initial default position
LatLng pt = new LatLng(38.547279, -121.461019);
mMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
// move map to a coordinate
CameraUpdate cu = CameraUpdateFactory.newLatLng(pt);
mMap.moveCamera(cu);
cu = CameraUpdateFactory.zoomTo(14);
mMap.moveCamera(cu);
mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() {
@Override
public void onMapClick(LatLng latLng) {
mMap.addCircle(new CircleOptions().radius(4).strokeColor(Color.BLUE).center(latLng));
}
});
// while the camera is moving just move the grid (realign on idle)
mMap.setOnCameraMoveListener(new GoogleMap.OnCameraMoveListener() {
@Override
public void onCameraMove() {
((GridView) findViewById(R.id.grid_any)).setAlignment(
null, mMap.getProjection(), mMap.getProjection().getVisibleRegion());
}
});
// on idle fetch the grid using the screen center point
mMap.setOnCameraIdleListener(new GoogleMap.OnCameraIdleListener() {
@Override
public void onCameraIdle() {
Log.d(TAG,"idle");
final LatLng centerOfGridCell = mMap.getCameraPosition().target;
if (!gridSet || mMap.getCameraPosition().zoom < 10) {
getGrid(centerOfGridCell, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Log.d(TAG, "reqpt: " + centerOfGridCell + " volley response: " + response);
try {
JSONObject jsonObject = new JSONObject(response);
JSONArray jsonArray = jsonObject.getJSONArray("lines");
JSONObject firstList = jsonArray.getJSONObject(1);
JSONObject firstPt = firstList.getJSONObject("start");
String lat = firstPt.getString("lat");
String lng = firstPt.getString("lng");
Log.d(TAG, "lat: " + lat + " lng: " + lng);
LatLng alignmentPt = new LatLng(Double.parseDouble(lat), Double.parseDouble(lng));
Projection p = mMap.getProjection();
VisibleRegion vr = p.getVisibleRegion();
((GridView) findViewById(R.id.grid_any)).setAlignment(alignmentPt, p, vr);
if (polygon != null) {
polygon.remove();
}
// take alignment point and draw 3 meter square polygon
LatLng pt1 = SphericalUtil.computeOffset(alignmentPt, 3, 90);
LatLng pt2 = SphericalUtil.computeOffset(pt1, 3, 180);
LatLng pt3 = SphericalUtil.computeOffset(pt2, 3, 270);
polygon = mMap.addPolygon(new PolygonOptions().add(alignmentPt,
pt1, pt2, pt3, alignmentPt)
.strokeColor(Color.BLUE).strokeWidth(4).fillColor(Color.BLUE));
} catch (Exception e) {
e.printStackTrace();
}
}
});
gridSet = true;
}
}
});
}
// Issue request to w3w - REMEMBER TO REPLACE **YOURKEY** ...
private void getGrid(LatLng pt, Response.Listener<String> listener) {
// something 9 meters to east
LatLng otherPt = SphericalUtil.computeOffset(pt, 6.0, 225);
String bboxStr = Double.toString(pt.latitude)+","+
Double.toString(pt.longitude)+","+
Double.toString(otherPt.latitude)+","+
Double.toString(otherPt.longitude);
RequestQueue rq = Volley.newRequestQueue(this);
String url = "https://api.what3words.com/v2/grid?bbox="+bboxStr+"&format=json&key=YOURKEY";
Log.d(TAG,"url="+url);
StringRequest req = new StringRequest(Request.Method.GET, url, listener, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG, "volley error: "+error);
}
});
rq.add(req);
}
Grid View
The grid view extends View and is in the map layout as a sibling to the map fragment.
The interestings parts are:
setAlignment - here the screen pixel extent of 3 meters is computed using the
SphericalUtil class. This screen pixel dimension representing a 3 meter extent
(at the provided reference location) is then used to align the grid by computing x/y offsets (which are then used in the onDraw). Note this auto-scales the grid using the 'SphericalUtil.computeOffset' utility to measure a point 3 meters east and as a result compute the screen pixel equivalent of 3 meters.
onDraw - here the translate method of canvas is used to repeat the grid shape starting at the computed offset (in setAlignment).
public class GridView extends View {
private static final String TAG = GridView.class.getSimpleName();
BitmapDrawable bm;
Bitmap bitmap;
GradientDrawable gd;
int offsetX = 0;
int offsetY = 0;
private int width = 16;
public GridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setWidth(int w) {
width = w;
render();
invalidate();
}
private void render() {
setShape();
if (gd != null) {
bitmap = drawableToBitmap(gd);
bm = new BitmapDrawable(getResources(), bitmap);
bm.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
bm.setBounds(0, 0, width, width);
}
}
Point startPt;
LatLng savedAlignmentPt;
public void setAlignment(LatLng alignmentPt, Projection p, VisibleRegion vr) {
if (alignmentPt == null) {
alignmentPt = savedAlignmentPt;
}
if (alignmentPt == null) {
return;
}
// the alignment point is the a corner of a grid square "near" the center of the screen
savedAlignmentPt = alignmentPt;
// compute how many screen pixels are in 3 meters using alignment point
startPt = p.toScreenLocation(alignmentPt);
LatLng upperRight = SphericalUtil.computeOffset(alignmentPt, 3, 90);
Point upperRtPt = p.toScreenLocation(upperRight);
int pixelsOf3meters = upperRtPt.x - startPt.x;
// don't draw grid if too small
if (pixelsOf3meters < 16) {
return;
}
setWidth(pixelsOf3meters);
// startPt is screen location of alignment point
offsetX = (pixelsOf3meters - (startPt.x % pixelsOf3meters));
offsetY = (pixelsOf3meters - (startPt.y % pixelsOf3meters));
invalidate();
}
private void setShape() {
int left, right, top, bottom;
gd = new GradientDrawable();
gd.setShape(GradientDrawable.RECTANGLE);
gd.setSize(width, width);
gd.setStroke(2, Color.argb(0xff, 0xcc, 0x22, 0x22));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Rect rect = canvas.getClipBounds();
final int cWidth = canvas.getWidth();
final int cHeight = canvas.getHeight();
if (bm == null) {
return;
}
final Rect bmRect = bm.getBounds();
if (bmRect.width() <= 8 || bmRect.height() <= 8) {
return;
}
final int iterX = iterations(cWidth, -offsetX, bmRect.width());
final int iterY = iterations(cHeight, -offsetY, bmRect.height());
canvas.translate(-offsetX, -offsetY);
for (int x = 0; x < iterX; x++) {
for (int y = 0; y < iterY; y++) {
bm.draw(canvas);
canvas.translate(.0F, bmRect.height());
}
canvas.translate(bmRect.width(), -bmRect.height() * iterY);
}
}
private static int iterations(int total, int start, int side) {
final int diff = total - start;
final int base = diff / side;
return base + (diff % side > 0 ? 1 : 0);
}
public static Bitmap drawableToBitmap (Drawable drawable) {
Bitmap bitmap = null;
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
if(bitmapDrawable.getBitmap() != null) {
return bitmapDrawable.getBitmap();
}
}
if(drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel
} else {
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
}
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
}
Notes:

I'm having my own battle with the w3w grid API. When I compute the distance between start/end points for each point in the list returned, I get 4.24264 meters so clearly I'm not getting something. Here's a simple method to show results and the screen shot (white=current center used in request, any other color=start-end of a point pair in list; end points have black outline). Here also it becomes clear which point is used to align the grid.
Interestingly though, the start of one "line" does appear to be 3 meters from the start of the next line (compare red-start to blue-start):

Code:
// plot each point as a circle
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject startPt = jsonArray.getJSONObject(i).getJSONObject("start");
JSONObject endPt = jsonArray.getJSONObject(i).getJSONObject("end");
LatLng start = new LatLng(Double.parseDouble(startPt.getString("lat")), Double.parseDouble(startPt.getString("lng")));
LatLng end = new LatLng(Double.parseDouble(endPt.getString("lat")), Double.parseDouble(endPt.getString("lng")));
int c = colors[(i % colors.length)];
mMap.addCircle(new CircleOptions().center(start).strokeColor(c).fillColor(c).radius(1));
mMap.addCircle(new CircleOptions().center(end).strokeColor(Color.BLACK).fillColor(c).radius(1).strokeWidth(2));
Log.d(TAG, "d = "+SphericalUtil.computeDistanceBetween(start,end));
}
mMap.addCircle(new CircleOptions().center(centerOfGridCell).strokeColor(Color.WHITE).radius(1).strokeWidth(4));