/*************************************************************************
* Copyright (c) 2015 Lemberg Solutions
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**************************************************************************/
package com.ls.widgets.map.model;
import java.util.ArrayList;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import com.ls.widgets.map.config.OfflineMapConfig;
import com.ls.widgets.map.interfaces.OnGridReadyListener;
import com.ls.widgets.map.providers.TileProvider;
import com.ls.widgets.map.utils.OfflineMapUtil;
public class Grid
{
private static final String TAG = "Grid";
//Tile manager is responsible for managing image tiles and free memory
private TileProvider tileProvider;
private OnGridReadyListener onReadyListener;
//View where drawing will be occurred
private View parentView;
//Model of the grid.
private ArrayList<Cell>cells;
//Amount of tile columns
private int colCount;
//Amount of tile rows
private int rowCount;
//Size of a square tile in pixels
private int tileSize;
//Zoom level
private int zoomLevel;
//Scale
private double softScale;
//Original image (max zoom) height in pixels
private int imageHeight;
//Original image (max zoom) width in pixels
private int imageWidth;
//Maximum possible zoom level
private int maxZoomLevel;
// For caching purpose only
private boolean loadTiles;
private Rect cachedDrawRect;
private int screenXCapacity;
private int screenYCapacity;
private Point firstVisibleCell;
private Point lastVisibleCell;
private Rect gridWindow;
private Rect gridWindowBigger;
private int cellsToDrawCount;
private int cellsReadyCount;
private double intScale;
private double scale;
public Grid(View parent, OfflineMapConfig config, TileProvider tileProvider, int initZoomLevel)
{
this.zoomLevel = initZoomLevel;
this.imageWidth = config.getImageWidth();
this.imageHeight = config.getImageHeight();
this.maxZoomLevel = OfflineMapUtil.getMaxZoomLevel(imageWidth, imageHeight);
this.softScale = 1.0;
this.intScale = 1.0;
this.scale = 1.0;
this.tileSize = config.getTileSize();
this.parentView = parent;
this.tileProvider = tileProvider;
loadTiles = true;
rowCount = getRowCount();
colCount = getColCount();
calcCellsInScreen();
// Creating the two points here and then reusing them in onDraw() in order to
// avoid of extensive use of garbage collector
firstVisibleCell = new Point(0,0);
lastVisibleCell = new Point(0,0);
gridWindow = new Rect(0,0,0,0);
gridWindowBigger = new Rect(0,0,0,0);
initGrid(initZoomLevel, tileSize);
}
private void calcCellsInScreen() {
screenXCapacity = (int)(parentView.getWidth() / ((float)tileSize * scale)) / 2;
screenYCapacity = (int)(parentView.getHeight() / ((float)tileSize * scale)) / 2;
}
private void initGrid(int zoomLevel, int tileSize)
{
if (Looper.myLooper() == null) {
throw new IllegalThreadStateException("Should be called from UI thread");
}
int size = colCount*rowCount;
cells = new ArrayList<Cell>(size);
for (int i=0; i<size; ++i) {
int currRow = i/colCount;
Cell cell = new Cell(this, tileProvider, zoomLevel, i - currRow * colCount, currRow, tileSize, scale);
cells.add(cell);
}
}
public void setTileProvider(TileProvider tileManager)
{
for (Cell cell:cells) {
cell.setTileProvider(tileManager);
}
}
public void draw(Canvas canvas, Paint paint, Rect drawingRect)
{
cachedDrawRect = drawingRect;
if (cells == null)
return;
gridWindow = getGridWindow(/*in*/ drawingRect);
gridWindowBigger = getGridWindow(/*in*/ drawingRect);//getRridWindowBigger(gridWindow);
// Log.d(TAG, "Paint window: " + gridWindow.toShortString() + "Cache window: " + gridWindowBigger.toShortString()+
// " Drawing rect: " + drawingRect.toShortString() +
// "Cols: " + colCount + " Rows: " + rowCount);
//
cellsToDrawCount = 0;
cellsReadyCount = 0;
for (int row=gridWindowBigger.top; row<=gridWindowBigger.bottom; ++row) {
for (int col=gridWindowBigger.left; col<=gridWindowBigger.right; ++col) {
Cell cell = cells.get((int) (col + row*colCount));
if (cell != null) {
if (col >= gridWindow.left && col <= gridWindow.right &&
row >= gridWindow.top && row <= gridWindow.bottom) {
cell.draw(canvas, paint, 0, 0);
if (!cell.isReady()) {
cellsToDrawCount += 1;
}
} else {
cell.cacheImage(0, 0);
}
}
}
}
}
private Rect getRridWindowBigger(Rect gridWindow)
{
gridWindowBigger.left = gridWindow.left - screenXCapacity;
if (gridWindowBigger.left < 0)
gridWindowBigger.left = 0;
gridWindowBigger.right = gridWindow.right + screenXCapacity;
if (gridWindowBigger.right >= colCount)
gridWindowBigger.right = colCount - 1;
gridWindowBigger.top = gridWindow.top - screenYCapacity;
if (gridWindowBigger.top < 0)
gridWindowBigger.top = 0;
gridWindowBigger.bottom = gridWindow.bottom + screenYCapacity;
if (gridWindowBigger.bottom >= rowCount)
gridWindowBigger.bottom = rowCount - 1;
return gridWindowBigger;
}
public int getHeight()
{
return (int)Math.ceil(imageHeight * getScale());
}
public int getOriginalHeight()
{
return imageHeight;
}
public int getOriginalWidth()
{
return imageWidth;
}
public double getScale()
{
if (maxZoomLevel == zoomLevel)
return 1.0f * scale;
return (1.0/(Math.pow(2,maxZoomLevel - zoomLevel)))*scale;
}
public int getWidth()
{
return (int) Math.ceil(imageWidth * getScale());
}
public int getZoomLevel()
{
return zoomLevel;
}
public int getMaxZoomLevel()
{
return maxZoomLevel;
}
public View getParentView()
{
return parentView;
}
public void setLoadTiles(boolean loadTiles)
{
applyLoadTileState(loadTiles);
}
public boolean isLoadTiles()
{
return this.loadTiles;
}
public int getMinZoomLevel()
{
return 0;
}
public void setSoftScale(float newScale)
{
if (newScale == 0.0)
throw new IllegalArgumentException();
this.softScale = newScale;
this.scale = softScale * intScale;
applyScale(this.scale);
}
public void setOnReadyListener(OnGridReadyListener listener)
{
//tileManager.setOnReadyListener(listener);
this.onReadyListener = listener;
}
protected int getColCount()
{
return (int)(Math.ceil(((float)OfflineMapUtil.getScaledImageSize(maxZoomLevel, zoomLevel, imageWidth) / (float)tileSize)));
}
protected int getRowCount()
{
return (int)((float)Math.ceil(((float)OfflineMapUtil.getScaledImageSize(maxZoomLevel, zoomLevel, imageHeight) / (float)tileSize)));
}
public double getSoftScale()
{
return softScale;
}
private Point getBottomRightVisibleCell(Rect drawingRect, Point point)
{
if (point == null || drawingRect == null)
throw new IllegalArgumentException();
Point topLeft = getTopLeftVisibleCell(drawingRect, point);
int cols = (int) Math.ceil(((float)drawingRect.width() / ((float)tileSize * scale))) + 2;
int rows = (int) Math.ceil(((float)drawingRect.height() / ((float)tileSize * scale))) + 1;
point.x = topLeft.x + cols;
point.y = topLeft.y + rows;
if (point.x >= colCount)
point.x = colCount-1;
if (point.y >= rowCount)
point.y = rowCount-1;
return point;
}
private Point getTopLeftVisibleCell(Rect drawingRect, Point point)
{
if (point == null || drawingRect == null)
throw new IllegalArgumentException();
point.x = (int) Math.floor(((float)drawingRect.left / ((float)tileSize * scale))) - 1;
point.y = (int) Math.floor(((float)drawingRect.top / ((float)tileSize * scale))) - 1;
if (point.x < 0) {
point.x = 0;
}
if (point.y < 0) {
point.y = 0;
}
return point;
}
private synchronized Rect getGridWindow(Rect drawRect)
{
if (drawRect == null || firstVisibleCell == null || lastVisibleCell == null)
throw new IllegalArgumentException();
getTopLeftVisibleCell(drawRect, firstVisibleCell);
getBottomRightVisibleCell(drawRect, lastVisibleCell);
// window.top = firstVisibleCell.y - screenYCapacity;
// if (window.top< 0) window.top= 0;
//
// window.left = firstVisibleCell.x - screenYCapacity;
// if (window.left < 0) window.left = 0;
//
// window.bottom = lastVisibleCell.y + screenYCapacity;
// if (window.bottom > rowCount) window.bottom = rowCount;
//
// window.right = lastVisibleCell.x + screenYCapacity;
// if (window.right > colCount) window.right = colCount;
// return window;
gridWindow.top = firstVisibleCell.y;
gridWindow.left = firstVisibleCell.x;
gridWindow.bottom = lastVisibleCell.y;
gridWindow.right = lastVisibleCell.x;
return gridWindow;
}
private void applyScale(double scale)
{
int size = cells.size();
calcCellsInScreen();
for (int i=0; i<size; ++i) {
Cell cell = cells.get(i);
cell.setScale(scale);
}
}
private void applyLoadTileState(boolean allowTileLoad)
{
int size = cells.size();
synchronized (cells) {
for (int i=0; i<size; ++i) {
Cell cell = cells.get(i);
cell.setLoadImage(allowTileLoad);
}
}
}
public void freeResources()
{
if (cachedDrawRect == null)
return;
gridWindow = getGridWindow(cachedDrawRect);
gridWindowBigger = getRridWindowBigger(gridWindow);
for (int row=0; row<rowCount; ++row) {
for (int col=0; col<colCount; ++col) {
if (!(col >= gridWindowBigger.left && col <=gridWindowBigger.right && row >= gridWindowBigger.top && row <= gridWindowBigger.bottom)) {
Cell cell = cells.get((int) (col + row*colCount));
if (cell != null) {
cell.freeResources();
}
}
}
}
}
void onCellReady(Cell cell)
{
cellsReadyCount += 1;
if (cellsReadyCount == cellsToDrawCount) {
if (onReadyListener != null) {
Log.d(TAG, "OnReady!");
onReadyListener.onReady();
}
}
}
public void setInternalScale(float newScale)
{
if (newScale == 0.0)
throw new IllegalArgumentException();
intScale = newScale;
this.scale = softScale * intScale;
applyScale(this.scale);
}
public double getIntScale() {
return intScale;
}
//
// public void setInternalScale(float newScale) {
// softScale *= newScale;
// }
}