Version 2.4.0.

- Added ImageFileCache and FileDbCache for WinRT
- Improved TileImageLoader
- Removed TileContainer, TileLayer can be added as MapBase child
- Removed attached property MapPanel.ViewportPosition
This commit is contained in:
ClemensF 2014-11-19 21:11:14 +01:00
parent f04025067c
commit 3b6545e738
84 changed files with 5504 additions and 1940 deletions

BIN
Caching/FileDb/FileDb.dll Normal file

Binary file not shown.

1870
Caching/FileDb/FileDb.xml Normal file

File diff suppressed because it is too large Load diff

Binary file not shown.

983
Caching/FileDb/Help.html Normal file
View file

@ -0,0 +1,983 @@
<html>
<head>
<meta http-equiv="Content-Language" content="en-au">
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
<title>FileDb Overview</title>
</head>
<body>
<b>
<p><font face="Arial" size="4" color="#0066CC">Overview</font></p>
</b>
<p><font face="Arial">FileDb is a simple database designed as a simple
local database solution for Xamarin Cross-Platform phone (Android, IOS, WinPhone)
or any .NET application.&nbsp; <u>FileDb is a No-SQL database</u>
meant for use as a local data store for applications.&nbsp;
Here are some important points about FileDb:</font></p>
<ul>
<li>
<font face="Arial">Stores one table per file, including its index</font></li>
<li>
<font face="Arial">Extremely Lightweight - less than 50K</font></li>
<li>
<font face="Arial">Supports field types Int, UInt, Bool, String, Byte,
Float, Double and DateTime and also arrays of the same
types</font></li>
<li>
<font face="Arial">Index supports a single Primary Key field (optional)</font></li>
<li>
<font face="Arial">Compiled versions for Windows Phone
RT/PCL,
.NET</font></li>
<li>
<font face="Arial">FileDb is VERY FAST</font></li>
<li>
<font face="Arial">FileDb is FREE to use in your applications</font></li>
<li>
<font face="Arial">Use with LINQ to Objects to achieve full relational
capability</font></li>
</ul>
<p><font face="Arial">FileDb was specifically designed to use only native
.NET data types so there would no need to translate
between database storage and the CLR data types.&nbsp;
So you can just as easily read/write a String[] field as
you would an Int field.&nbsp; Another feature is that a
database file created on any .NET platform will work on
any other.&nbsp; So you can create a database file on
your Windows machine and it can be used in a Silverlight
or Windows Phone app.</font></p>
<p><font color="#0066CC" face="Arial"><b>LINQ + FileDb gives you full
relational database capability</b></font></p>
<p><font face="Arial">Even though FileDb is a &quot;flat-file&quot; database, using
LINQ it becomes fully relational!&nbsp; LINQ
to Objects allows you to join Tables together just as
you would do in SQL. All of the power of LINQ is
available to you: Joins, Grouping, Sum - the lot.&nbsp;
(See
the examples below.)</font></p>
<p><font face="Arial">FileDb also has a built-in
query filter parser so you can write SQL-like
filter expressions to make filtering data easy, like
this:</font></p>
<blockquote>
<p><b>
<font color="#663300" face="Courier New" size="2">
string filter = &quot;FirstName IN (&#39;Cindy&#39;, &#39;John&#39;) AND Age &gt; 32&quot;</font></b></p>
</blockquote>
<p><font face="Arial">Use FileDb in your .NET and mobile
applications where you need a searchable, updatable
local database.</font><p>
<b><font face="Arial" color="#0066CC">Use FileDb with Xamarin cross-platform app
development</font></b><p><font face="Arial">Most phone apps only require a
simple database.&nbsp; By purchasing a source code license you can compile
FileDb into your IOS, Android and Windows Phone projects and use the same exact
data layer code for them all.&nbsp; This is much easier than using Sqlite
wrappers.&nbsp; With FileDb's built in encryption you can have everything you
need to have a secure data layer.</font></p>
<p>
<font color="#0066CC" face="Arial"><b>FileDb Database Overview</b></font></p>
<p><font face="Arial">FileDb is a simple database designed for use on any .NET platform such as Windows
Phone and Silverlight, but its also great for any .NET app where simple local
database storage is needed. For example, instead of using XML config files you
could use a FileDb database to store and retrieve application data much more
efficiently. FileDb allows only a single table per database file, so when we
talk about a FileDb database we really mean a single table with an index. The
index is stored in the file with the data, and allows an optional Primary Key.</font></p>
<p><font face="Arial">FileDb is NOT a relational database - it is NO-SLQ,
meaning you can't directly issue SQL commands for querying, adding or updating. However,
you CAN use LINQ with
FileDb to get full relational query capabilities.&nbsp;&nbsp; And FileDb does include an Expression Parser which parses SQL-like filter
expressions, which makes searching, updating and deleting very easy - see below for an example.</font></p>
<p><font face="Arial"><u>And FileDb supports using powerful Regular Expressions for
filtering.</u></font></p>
<p><font face="Arial"><u>FileDb supports AES encryption</u> at the record level. This
means the database schema is not encrypted (field names, etc.), but each record
is entirely encrypted. Encryption is &quot;all or nothing&quot;, meaning it expects that
either all records are encrypted or all records are not encrypted. You turn
encryption on by passing an encryption key when opening the database.</font></p>
<u>
<p dir="ltr"><font face="Arial">FileDb is thread-safe for multithreading environments, so it can be accessed from
multiple threads at the same time without worrying about database corruption.</font></u></p>
<p><font face="Arial">FileDb databases can only be opened by a single
application. Any attempt to open the file when already open will fail.&nbsp;
This makes sense since its meant for use by a single application at a time
(FileDb is not meant as a multi-user database, such as SQL Server Express).<br>
&nbsp;</font></p>
<p><b><font color="#0066CC" face="Arial">FileDb Classes</font></b></p>
<p><font face="Arial">The main FileDb classes are: <b>FileDb</b>, <b>Table</b>,
<b>Field</b> and <b>Record</b>. </font> </p>
<dir>
<b>
<li><font face="Arial">FileDb</font></b><font face="Arial">: Represents a database file. All database operations are
initiated through this class.<br>
&nbsp;</li>
</font>
<b>
<li><font face="Arial">Table</font></b><font face="Arial">: Represents a two
dimensional dataset returned from a query. A Table consists of <b>Fields </b>
and <b>Records</b>.<br>
&nbsp;</li>
</font>
<b>
<li><font face="Arial">Field</font></b><font face="Arial">: Defines the
properties of the table column, such as Name and DataType.<br>
&nbsp;</li>
</font>
<b>
<li><font face="Arial">Fields</font></b><font face="Arial">: A List of Field
objects.<br>
&nbsp;</font></li>
<li>
<font face="Arial">
<b>
Record</b>: A list of data objects represents a single row in a Table.&nbsp;
Implements IEnumerable and the Data property which is used for DataBinding.<br>
&nbsp;</li>
</font>
<b>
<li><font face="Arial">Records</font></b><font face="Arial">: A List of
Record objects.<br>
&nbsp;</li>
</font>
<b>
<li><font face="Arial">FieldValues</font></b><font face="Arial">: A simple
Name/Value pair Dictionary. Use this class when adding and updating records.<br>
&nbsp;</li>
</font>
<b>
<li><font face="Arial">FilterExpression</font></b><font face="Arial">: Used
to filter records for query, update and delete.<br>
&nbsp;</li>
</font>
<b>
<li><font face="Arial">FilterExpressionGroup</font></b><font face="Arial">:
Used to create compound expressions by grouping FilterExpressions and
FilterExpressionGroups.<br>
&nbsp;</font></li>
</dir>
<p><b><font color="#0066CC" face="Arial">Database Fields</font></b></p>
<p><font face="Arial">Fields (or Columns) can be of several common types:
String, Int, UInt, Bool, Byte, Float, Double and DateTime, or can also be an array of any of
these types.</font></p>
<p><font face="Arial">Int Fields can be AutoIncrementing, and you can optionally
specify one field to be Primary Key (it must be of type Int or String).</font></p>
<p><font face="Arial">FileDb doesn't support the notion of NULL fields for the
non-array type. Only array type fields can have NULL values. The non-array field
values will always have a value, either zero or empty.</font></p>
<p><font color="#0066CC" face="Arial"><b>FileDb Records</b></font></p>
<p><font face="Arial">FileDb supports two methods of data retrieval.&nbsp; You can say the
&quot;default&quot; way is with the built-in Record and Records classes.&nbsp; Think of
Record as the .NET DataRow class, and think of Table as a DataTable.&nbsp; Table
is a list of Records, and a Record holds the actual values. You access Field
values using indexing just as you would a DataRow, like this:</font></p>
<blockquote>
<font FACE="Courier New">
<table border="0" width="100%" id="table25" bgcolor="#FBEDBB" cellpadding="10">
<tr>
<td>
<font face="Courier New" size="2">FileDb employeesDb = new FileDb();<br>
employeesDb.Open( Employees.fdb&quot; );<br>
<br>
Table employees = employeesDb.SelectAllRecords();<br>
Record record =
employees[0];<br>
int id = (int) record[&quot;EmployeeId&quot;];<br>
// or<br>
id
= (int) record[0];</font></td>
</tr>
</table>
</font>
</blockquote>
<p><font face="Arial">To use a Table with LINQ, you do this:</font></p>
<blockquote>
<font FACE="Courier New">
<table border="0" width="100%" id="table26" bgcolor="#FBEDBB" cellpadding="10">
<tr>
<td>
<font FACE="Courier New" size="2">var recs = from e in
employees<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; where (string)
e[&quot;FirstName&quot;] == &quot;John&quot;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select e;</font></td>
</tr>
</table>
</font>
</blockquote>
<p><font face="Arial">Notice we have to cast the record value to a string.&nbsp;
This is because, just like with the DataRow, Record
values are all type object.</font></p>
<p><font color="#0066CC" face="Arial"><b>Records and Custom Objects</b></font></p>
<p><font face="Arial">Records are great because they require no additional
programming and they work with LINQ, albeit with some
casting.&nbsp; But you can use your own custom classes
if you want because FileDb has template (generic)
overloads for each of the SelectRecords methods.&nbsp;
You only need to create a class with public properties
which match the names of the fields you want to use.&nbsp;
Here&#39;s an example using the Employees table.</font></p>
<blockquote>
<font FACE="Courier New">
<table border="0" width="100%" id="table27" bgcolor="#FBEDBB" cellpadding="10">
<tr>
<td>
<font FACE="Courier New" size="2">public class Employee<br>
{<br>
&nbsp;&nbsp;&nbsp; public int EmployeeID { get; set; }<br>
&nbsp;&nbsp;&nbsp; public string LastName { get; set; }<br>
&nbsp;&nbsp;&nbsp;
public string
FirstName { get; set; }<br>
&nbsp;&nbsp;&nbsp;
public string Title {
get; set; }<br>
&nbsp;&nbsp;&nbsp;
public string
TitleOfCourtesy { get; set; }<br>
&nbsp;&nbsp;&nbsp;
public DateTime
BirthDate { get; set; }<br>
&nbsp;&nbsp;&nbsp;
public DateTime
HireDate { get; set; }<br>
&nbsp;&nbsp;&nbsp;
public string Address
{ get; set; }<br>
&nbsp;&nbsp;&nbsp;
public string City {
get; set; }<br>
&nbsp;&nbsp;&nbsp;
public string Region {
get; set; }<br>
&nbsp;&nbsp;&nbsp;
public string
PostalCode { get; set; }<br>
&nbsp;&nbsp;&nbsp;
public string Country
{ get; set; }<br>
&nbsp;&nbsp;&nbsp;
public string
HomePhone { get; set; }<br>
&nbsp;&nbsp;&nbsp;
public string
Extension { get; set; }<br>
&nbsp;&nbsp;&nbsp;
public Byte[] Photo {
get; set; }<br>
&nbsp;&nbsp;&nbsp;
public string Notes {
get; set; }<br>
&nbsp;&nbsp;&nbsp;
public int ReportsTo {
get; set; }<br>
}</font></td>
</tr>
</table>
</font>
</blockquote>
<p><font face="Arial">The templated SelectRecords versions return a IList&lt;T&gt;
where T is your custom type.</font></p>
<blockquote>
<font FACE="Courier New">
<table border="0" width="100%" id="table28" bgcolor="#FBEDBB" cellpadding="10">
<tr>
<td>
<font FACE="Courier New" size="2">
IList&lt;Employee&gt;
employees = employeesDb.SelectAllRecords&lt;Employee&gt;();<br>
Employee employee
= employees[0];<br>
int id = Employee.EmployeeId;<br>
<br>
var emps = from e in
employees<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; where e.FirstName
== &quot;John&quot;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select e;</td>
</tr>
</table>
</font>
</blockquote>
<p><font face="Arial">As you can see, this is much cleaner code.&nbsp; And
its actually more efficient since the Record class has
more overhead because its not as simple.</font></p>
<p><b><font color="#0066CC" face="Arial">Searching and Filtering</font></b></p>
<p><font face="Arial">FileDb uses FilterExpressions and
FilterExpressionGroups to filter records in queries and
updates. We use FilterExpressions for simple queries
which consist of a single field comparison (field =
&#39;value&#39;) and we use FilterExpressionGroups for compound
expressions, where multiple expressions and grouping are
required. You can add either FilterExpressions or
FilterExpressionGroups to a FilterExpressionGroup, thus
creating complex expresssions (FileDb processes
FilterExpressionGroups recursively).</font></p>
<p><font face="Arial">You can either create your own manually in code or
use the built-in Expression Parser to create them for
you. The Expression Parser recognizes standard SQL
comparison operators. You can see it used in the
examples below. It also recognizes REGEX, which
uses Regular Expressions, and CONTAINS which uses <b>
String.Contains</b>. See the section on
Regular Expressions below for more info. Field names
prefixed with ~ specifies no-case comparison (for
strings only).</font></p>
<p><font face="Arial">Each time you use () around an expression, a new
FilterExpressionGroup will be created. The inner-most expressions are evaluated
first, just as in SQL.</font></p>
<b>
<p><font color="#333333" face="Arial">Example 1: Create a FilterExpression</font></p>
</b>
<blockquote>
<font FACE="Courier New">
<table border="0" width="100%" id="table29" bgcolor="#FBEDBB" cellpadding="10">
<tr>
<td>
<font size="2" FACE="Courier New">// build an expression manually<br>
FilterExpression searchExp = new FilterExpression( &quot;LastName&quot;, &quot;Peacock&quot;,
Equality.Equal );<br>
<br>
// build the same expression using the
parser<br>
searchExp = FilterExpression.Parse( &quot;LastName = 'Peacock'&quot; ); <br>
Table table = employeesDb.SelectRecords(
searchExp,
new string[] { &quot;ID&quot;, &quot;LastName&quot; } );<br>
<br>
// Or you can simply pass the string filter
directly - a FilterExpression will be
created in the same way as above<br>
<br>
table = employeesDb.SelectRecords( &quot;LastName
= &#39;Peacock&#39;&quot;, new string[] { &quot;ID&quot;, &quot;LastName&quot;
} );<br>
<br>
foreach( Record record in table )<br>
{<br>
&nbsp;&nbsp;&nbsp; foreach( object value in record )<br>
&nbsp;&nbsp;&nbsp; {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Debug.WriteLine( value );<br>
&nbsp;&nbsp;&nbsp;
}<br>
}</font></td>
</tr>
</table>
</font>
</blockquote>
<b>
<p><font color="#333333" face="Arial"><br>
Example 2: Create a FilterExpressionGroup</font></p>
</b>
<p><font face="Arial">This example creates two identical FilterExpressionGroups,
one using the Expression Parser and the other with code.</font></p>
<blockquote>
<font FACE="Courier New">
<table border="0" width="100%" id="table30" bgcolor="#FBEDBB" cellpadding="10">
<tr>
<td><font size="2" FACE="Courier New">// For string fields there are
2 ways to specify </font><font size="2" FACE="Courier New">no-case
comparisons: you can prefix fieldnames with ~ or you can use ~= as
demonstrated below<br>
// The first form is needed when using the IN operator, eg.<br>
FilterExpressionGroup filterExpGrp =
FilterExpressionGroup.Parse( &quot;(FirstName ~= 'andrew' OR ~FirstName = 'nancy')
AND LastName = 'Fuller'&quot; );<br>
Table table = employeesDb.SelectRecords( filterExpGrp );<br>
<br>
// equivalent building it manually<br>
var fname1Exp = new FilterExpression( &quot;FirstName&quot;, &quot;andrew&quot;, Equality.Equal,
MatchType.IgnoreCase );<br>
var fname2Exp = new FilterExpression( &quot;FirstName&quot;, &quot;nancy&quot;, Equality.Equal,
MatchType.IgnoreCase );<br>
var lnameExp = new FilterExpression( &quot;LastName&quot;, &quot;Fuller&quot;, Equality.Equal );
// this constructor defaults to MatchType.UseCase<br>
var fnamesGrp = new FilterExpressionGroup();<br>
fnamesGrp.Add( BoolOp.Or, fname1Exp );<br>
fnamesGrp.Add( BoolOp.Or, fname2Exp );<br>
var allNamesGrp = new FilterExpressionGroup();<br>
allNamesGrp.Add( BoolOp.And, lnameExp );<br>
allNamesGrp.Add( BoolOp.And, fnamesGrp );<br>
<br>
table = employeesDb.SelectRecords( allNamesGrp );<br>
<br>
// or just pass the filter string directly<br>
<br>
table = employeesDb.SelectRecords( &quot;(FirstName ~= 'andrew' OR ~FirstName = 'nancy')
AND LastName = &#39;Fuller&#39;&quot; );<br>
&nbsp;</font></td>
</tr>
</table>
</font>
</blockquote>
<p><font face="Arial"><br>
FileDb supports these comparison operators:</font></p>
<font face="Arial" size="2">
<font face="Courier New">
<blockquote>
<table border="0" id="table31" style="border-collapse: collapse" bgcolor="#FBEDBB" cellpadding="7">
<tr>
<font face="Courier New" size="2">
<td><b>=</b></td>
</font><font face="Arial" size="2">
<td width="18">&nbsp;</td>
<font face="Courier New">
<td><font face="Arial">Equality</font></td>
</font></font>
</tr>
<tr>
<td>~<b>=</b></td>
</font><font face="Arial">
<td width="18">&nbsp;</td>
<font face="Courier New">
<td>No-case equality - strings only</td>
</tr>
<tr>
<td><font face="Courier New"><b>&lt;&gt;</b></font></td>
</font><font face="Arial">
<td width="18">&nbsp;</td>
<font face="Courier New">
<td><font face="Arial">Not Equal</font></td>
</tr>
<tr>
<td><font face="Courier New"><b>!=</b></font></td>
</font><font face="Arial">
<td width="18">&nbsp;</td>
<font face="Courier New">
<td><font face="Arial">Not Equal (same as &lt;&gt;)</font></td>
</tr>
<tr>
<td><font face="Courier New"><b>&gt;=</b></font></td>
</font><font face="Arial">
<td width="18">&nbsp;</td>
<font face="Courier New">
<td><font face="Arial">Greater than or Equal</font></td>
</tr>
<tr>
<td><font face="Courier New"><b>&lt;=</b></font></td>
</font><font face="Arial">
<td width="18">&nbsp;</td>
<font face="Courier New">
<td><font face="Arial">Less than or Equal</font></td>
</tr>
</font>
<font face="Courier New" size="2">
<tr>
<td><b><font face="Courier New">REGEX</font></b></td>
</font>
</font>
<font face="Arial" size="2">
<font face="Arial">
<td width="18">&nbsp;</td>
<td><font face="Arial">Use Regular Expression</font></td>
</tr>
<tr>
<font face="Arial" size="2">
<td><b><font face="Courier New">CONTAINS</font></b></td>
<font face="Arial">
<td width="18">&nbsp;</td>
<td><font face="Arial">Use the .NET String's Contains method</font></td>
</font>
</font>
</tr>
<tr>
<td><b><font face="Courier New">IN</font></b></td>
<td width="18">&nbsp;</td>
<td><font face="Arial">Creates a HashSet of values to use like SQL
IN operator</font></td>
</tr>
<tr>
<font face="Arial" size="2">
<td><b><font face="Courier New">NOT</font></b></td>
<font face="Arial">
<td width="18">&nbsp;</td>
<td><font face="Arial">Logical Negation, e.g NOT CONTAINS</font></td>
</font>
</font>
</tr>
</table>
</blockquote>
</font>
</font>
</font></font></font></font></font>
<font face="Arial" size="2">
<p>&nbsp;</p>
</font>
<p><b><font face="Arial" color="#0066CC">REGEX - </font>
<font color="#0066CC" face="Arial" size="3">Regular Expressions in searches and filtering</font></b></p>
<p><font face="Arial" size="3">FileDb supports using Regular Expressions. You can use any RegEx supported by
.NET. The Expression Parser supports MatchType.RegEx using the REGEX operator.&nbsp;
Internally, FileDb uses FilterExpressions to evaluate fields.&nbsp; You don&#39;t
need to use them because you can pass in filter strings and they&#39;ll be parsed
into FilterExpressions/FilterExpressionGroups for you.&nbsp; This is just to
show you how can create them manually if you want to.&nbsp; In
the example below, both FilterExpressionGroups are identical.</font></p>
<font FACE="Courier New" size="2">
<blockquote>
<font FACE="Courier New">
<table border="0" id="table32" bgcolor="#FBEDBB" cellpadding="10">
<tr>
<td><font size="2" FACE="Courier New">// Using the Expression Parser<br>
<br>
// You can use brackets around fieldnames if there are spaces in the
name<br>
FilterExpressionGroup filterExpGrp = FilterExpressionGroup.Parse( &quot;(~FirstName = 'steven' OR [FirstName]
REGEX 'NANCY') AND LastName = 'Fuller'&quot; );<br>
Table table = employeesDb.SelectRecords( filterExpGrp );<br>
<br>
// we can manually build the same FilterExpressionGroup<br>
var fname1Exp = FilterExpression.Parse( &quot;~FirstName = steven&quot; );<br>
var fname2Exp = new FilterExpression( &quot;FirstName&quot;, &quot;NANCY&quot;, Equality.Regex );<br>
var lnameExp = new FilterExpression( &quot;LastName&quot;, &quot;Fuller&quot;, Equality.Equal );<br>
var fnamesGrp = new FilterExpressionGroup();<br>
fnamesGrp.Add( BoolOp.Or, fname1Exp );<br>
fnamesGrp.Add( BoolOp.Or, fname2Exp );<br>
var allNamesGrp = new FilterExpressionGroup();<br>
allNamesGrp.Add( BoolOp.And, lnameExp );<br>
allNamesGrp.Add( BoolOp.And, fnamesGrp );<br>
<br>
table = employeesDb.SelectRecords( allNamesGrp );</font></td>
</tr>
</table>
</font>
</blockquote>
</font>
<font face="Arial" size="2">
<p>&nbsp;</p>
</font>
<p><b><font color="#0066CC" face="Arial" size="3">Using CONTAINS in searches and filtering</font></b></p>
<p><font face="Arial" size="3">FileDb supports using Regular Expressions. You can use any RegEx supported by
.NET. The Expression Parser supports MatchType.RegEx using the REGEX operator.&nbsp;
Internally, FileDb uses FilterExpressions to evaluate fields.&nbsp; You don&#39;t
need to use them because you can pass in filter strings and they&#39;ll be parsed
into FilterExpressions/FilterExpressionGroups for you.&nbsp; This is just to
show you how can create them manually if you want to.&nbsp; In
the example below, both FilterExpressionGroups are identical.</font></p>
<font FACE="Courier New" size="2">
<blockquote>
<font FACE="Courier New">
<table border="0" id="table42" bgcolor="#FBEDBB" cellpadding="10">
<tr>
<td><font size="2" FACE="Courier New">// You can use brackets around fieldnames if there are spaces in the
name<br>
FilterExpressionGroup filterExpGrp = FilterExpressionGroup.Parse( &quot;(~FirstName = 'steven' OR [FirstName]
CONTAINS 'NANC') AND LastName = 'Fuller'&quot; );<br>
Table table = employeesDb.SelectRecords( filterExpGrp );<br>
<br>
// we can manually build the same FilterExpressionGroup<br>
var fname1Exp = FilterExpression.Parse( &quot;~FirstName = steven&quot; );<br>
var fname2Exp = new FilterExpression( &quot;FirstName&quot;, &quot;NANCY&quot;, Equality.Contains );<br>
var lnameExp = new FilterExpression( &quot;LastName&quot;, &quot;Fuller&quot;, Equality.Equal );<br>
var fnamesGrp = new FilterExpressionGroup();<br>
fnamesGrp.Add( BoolOp.Or, fname1Exp );<br>
fnamesGrp.Add( BoolOp.Or, fname2Exp );<br>
var allNamesGrp = new FilterExpressionGroup();<br>
allNamesGrp.Add( BoolOp.And, lnameExp );<br>
allNamesGrp.Add( BoolOp.And, fnamesGrp );<br>
<br>
table = employeesDb.SelectRecords( allNamesGrp );</font></td>
</tr>
</table>
</font>
</blockquote>
</font>
<p>&nbsp;</p>
<p><b><font color="#0066CC" face="Arial" size="3">Sort Ordering</font></b></p>
<p><font face="Arial" size="3">Query methods allow for sorting the results by fields. To
get a reverse sort, prefix the sort field list with !. To get a no-case sort,
prefix with ~. To get both reverse and no-case sort, use both ! and ~.</font></p>
<p><font face="Arial" size="3">Example:</font></p>
<blockquote>
<font FACE="Courier New" size="2">
<table border="0" id="table33" bgcolor="#FBEDBB" cellpadding="10">
<tr>
<td>
<font FACE="Courier New" size="2">
Table table = employeesDb.SelectAllRecords( new string[] { &quot;ID&quot;, &quot;Firstname&quot;, &quot;LastName&quot;,
&quot;Age&quot; }, false, new string[] { &quot;~LastName&quot;, &quot;~FirstName&quot;, &quot;!Age&quot; } );</font></td>
</tr>
</table>
</font>
</blockquote>
<p><font color="#0066CC" face="Arial" size="3"><b>Selecting a Table from a Table</b></font></p>
<p><font face="Arial" size="3">Another very powerful feature of FileDb is the ability to select a Table from
another Table.&nbsp; This would allow you to be able to select data from a Table
after the database file has been closed, for example.</font></p>
<p><font face="Arial" size="3">Example:</font></p>
<blockquote>
<font FACE="Courier New" size="2">
<table border="0" id="table34" bgcolor="#FBEDBB" cellpadding="10">
<tr>
<td>
<font FACE="Courier New" size="2">
customersDb.Open( path + &quot;Customers.fdb&quot; );<br>
<br>
// select all fields and records from the database table<br>
Table customers = customersDb.SelectAllRecords();<br>
<br>
Table subCusts = customers.SelectRecords( &quot;CustomerID &lt;&gt; &#39;ALFKI&#39;&quot;,<br>
new string[] { &quot;CustomerID&quot;, &quot;CompanyName&quot;, &quot;City&quot; }, new string[] { &quot;~City&quot;, &quot;~CompanyName&quot;
} );</font></td>
</tr>
</table>
</font>
</blockquote>
<p><b><font color="#0066CC" face="Arial" size="3">Encryption</font></b></p>
<p><font face="Arial" size="3">Using encryption with FileDb is simple. You only need to
specify a string key when you open the database. After that everything is
automatic. The only caveat is you must set a key before you add any records.
Once a single record has been added without a key set you cannot later add
records with a key. Its all or nothing. Likewise, you cannot add records with
encryption and later add records without.&nbsp; You must set the encryption key
each time you add any records.</font></p>
<p><b><font color="#0066CC" face="Arial" size="3">Persisting Tables</font></b></p>
<p><font face="Arial" size="3">You can easily save a Table as a new database using
Table.SaveToDb.&nbsp; This method creates a new database file using the Fields
in the Table then populates it using the Records in the Table.&nbsp; For
example, you can select subsets of your data, save it as a new database and send
it over the Internet.</font></p>
<blockquote>
<font FACE="Courier New">
<table border="0" id="table35" bgcolor="#FBEDBB" cellpadding="10">
<tr>
<td><font FACE="Courier New" size="2">Table table = employeesDb.SelectAllRecords( new string[] { &quot;ID&quot;, &quot;Firstname&quot;, &quot;LastName&quot;
} );<br>
table.SaveToDb( &quot;Names.fdb&quot; );</font></td>
</tr>
</table>
</font>
</blockquote>
<p><font face="Arial" size="3">You can also save a Table to a database from the
FileDb Explorer. Just right-click on the Grid to show
the context menu and select the &quot;Create database from
Table...&quot; menu item.<br>
&nbsp;</font></p>
<p><font color="#0066CC" face="Arial" size="3"><b>Using LINQ to Objects with FileDb</b></font></p>
<p><font face="Arial" size="3">Microsoft has done an amazing job with LINQ.&nbsp;
They have invested a huge amount of time, effort and $
in this technology which allows you to query just about
any kind of data in a SQL-like way.&nbsp; We use LINQ
with FileDb to join Tables as we would using SQL.&nbsp;
The difference is that instead of doing it all in a
single step with SQL, we must do it in two steps.&nbsp;
First we select the data Tables from the database files
then we use LINQ to join them together.</font></p>
<p><font face="Arial" size="3">LINQ to Objects produces a list of anonymous types as
its result set.&nbsp; This is good because we get
strongly typed data objects which we can easily use in WPF/Silverlight apps.</font></p>
<p><font face="Arial" size="3">Here is an example of doing a simple select using
LINQ:</font></p>
<blockquote>
<font FACE="Courier New">
<table border="0" id="table36" bgcolor="#FBEDBB" cellpadding="10">
<tr>
<td><font face="Courier New" size="2">//
Using the IN operator.&nbsp; Notice the ~
prefix on the LastName field. This is how
<br>
// you can specify case insensitive searches<br>
<br>
Table
employees = employeesDb.SelectRecords( &quot;~LastName
IN (&#39;Fuller&#39;, &#39;Peacock&#39;)&quot; );<br>
<br>
var query =
from record in employees<br>
select new<br>
{<br>
&nbsp;&nbsp;&nbsp; ID = record[&quot;EmployeeId&quot;],<br>
&nbsp;&nbsp;&nbsp; Name = record[&quot;FirstName&quot;] + &quot; &quot; + record[&quot;LastName&quot;],<br>
&nbsp;&nbsp;&nbsp; Title = record[&quot;Title&quot;]<br>
};<br>
<br>
foreach( var rec in query )<br>
{<br>
&nbsp;&nbsp;&nbsp; Debug.WriteLine( rec.ToString() );<br>
}</font></td>
</tr>
</table>
</font>
<p><font face="Arial" size="3">The only thing LINQ did for us in this example was
gave us a typed list of anonymous objects.&nbsp; Here&#39;s
the same thing but with custom objects:</font></p>
<font FACE="Courier New">
<table border="0" id="table37" bgcolor="#FBEDBB" cellpadding="10">
<tr>
<td><font size="2">IList&lt;Employee&gt;</font><font face="Courier New" size="2"> employees
= employeesDb.SelectRecords&lt;Employee&gt;(
&quot;~LastName
IN (&#39;Fuller&#39;, &#39;Peacock&#39;)&quot; );<br>
<br>
var query =<br>
from e in employees<br>
select e;<br>
<br>
foreach( var emp in query )<br>
{<br>
&nbsp;&nbsp;&nbsp; Debug.WriteLine( emp.ToString() );<br>
}</font></td>
</tr>
</table>
</font>
</blockquote>
<p dir="ltr"><font face="Arial" size="3">Now lets tap into LINQ&#39;s real power to join tables together
like a SQL inner join.&nbsp; Notice in the following example we use the </font>
<font face="Courier New" size="2">FilterExpression.CreateInExpressionFromTable</font><font face="Arial"><font size="3">
method.&nbsp; We do this to get only the records we are going to need with LINQ.&nbsp;
So using FileDb with LINQ is a two step process.&nbsp; You first select the
records you will need then use them in the LINQ query.&nbsp; If your database
files are large, you can filter the records like this.&nbsp; Otherwise you can
just select all records.</font></p>
<blockquote>
<font FACE="Courier New">
<table border="0" id="table38" bgcolor="#FBEDBB" cellpadding="10">
<tr>
<td><font face="Courier New" size="2">FileDb customersDb = new FileDb(),<br>
ordersDb = new FileDb(),<br>
orderDetailsDb = new FileDb(),<br>
productsDb = new FileDb();<br>
<br>
customersDb.Open( &quot;Customers.fdb&quot; );<br>
ordersDb.Open( &quot;Orders.fdb&quot; );<br>
orderDetailsDb.Open( &quot;OrderDetails.fdb&quot; );<br>
productsDb.Open( &quot;Products.fdb&quot; );<br>
<br>
// get our target Customer records<br>
// Note that we should select only fields we
need from each table, but to keep the code<br>
// simple for this example we just pass in null for the field
list<br>
<br>
FilterExpression filterExp =
FilterExpression.Parse( &quot;CustomerID IN( &#39;ALFKI&#39;,
&#39;BONAP&#39; )&quot; );<br>
FileDbNs.Table customers =
customersDb.SelectRecords( filterExp );<br>
<br>
// now get only Order records for the target
Customer records<br>
// CreateInExpressionFromTable will create
an IN FilterExpression, which uses a HashSet
<br>
// for high efficiency when filtering
records<br>
&nbsp;<br>
filterExp =
FilterExpression.CreateInExpressionFromTable(
&quot;CustomerID&quot;, customers, &quot;CustomerID&quot; );<br>
FileDbNs.Table orders =
ordersDb.SelectRecords( filterExp );<br>
<br>
// now get only OrderDetails records for the
target Order records<br>
<br>
filterExp =
FilterExpression.CreateInExpressionFromTable(
&quot;OrderID&quot;, orders, &quot;OrderID&quot; );<br>
FileDbNs.Table orderDetails =
orderDetailsDb.SelectRecords( filterExp );<br>
<br>
// now get only Product records for the
target OrderDetails records<br>
<br>
filterExp =
FilterExpression.CreateInExpressionFromTable(
&quot;ProductID&quot;, orderDetails, &quot;ProductID&quot; );<br>
FileDbNs.Table products =
productsDb.SelectRecords( filterExp );<br>
<br>
// now we&#39;re ready to do the join<br>
<br>
var query =<br>
&nbsp;&nbsp;&nbsp; from custRec in customers<br>
&nbsp;&nbsp;&nbsp; join orderRec in orders on custRec[&quot;CustomerID&quot;]
equals orderRec[&quot;CustomerID&quot;]<br>
&nbsp;&nbsp;&nbsp; join orderDetailRec in orderDetails on
orderRec[&quot;OrderID&quot;] equals
orderDetailRec[&quot;OrderID&quot;]<br>
&nbsp;&nbsp;&nbsp; join productRec in products on
orderDetailRec[&quot;ProductID&quot;] equals
productRec[&quot;ProductID&quot;]<br>
&nbsp;&nbsp;&nbsp; select new<br>
&nbsp;&nbsp;&nbsp; {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ID = custRec[&quot;CustomerID&quot;],<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CompanyName = custRec[&quot;CompanyName&quot;],<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OrderID = orderRec[&quot;OrderID&quot;],<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OrderDate = orderRec[&quot;OrderDate&quot;],<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ProductName = productRec[&quot;ProductName&quot;],<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; UnitPrice = orderDetailRec[&quot;UnitPrice&quot;],<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Quantity = orderDetailRec[&quot;Quantity&quot;]<br>
&nbsp;&nbsp;&nbsp; };<br>
<br>
foreach( var rec in query )<br>
{<br>
&nbsp;&nbsp;&nbsp; Debug.WriteLine( rec.ToString() );<br>
}</font></td>
</tr>
</table>
</font>
<p><font face="Arial" size="3">Here&#39;s the same thing again using custom objects:</font></p>
<font FACE="Courier New">
<table border="0" id="table39" bgcolor="#FBEDBB" cellpadding="10">
<tr>
<td><font face="Courier New" size="2">// get our target Customer records<br>
<br>
FilterExpression filterExp =
FilterExpression.Parse( &quot;CustomerID IN( &#39;ALFKI&#39;,
&#39;BONAP&#39; )&quot; );<br>
IList&lt;Customer&gt; customers =
customersDb.SelectRecords&lt;Customer&gt;( filterExp );<br>
&nbsp;<br>
filterExp =
FilterExpression.CreateInExpressionFromTable&lt;Customer&gt;(
&quot;CustomerID&quot;, customers, &quot;CustomerID&quot; );<br>
IList&lt;Order&gt; orders =
ordersDb.SelectRecords&lt;Order&gt;( filterExp );<br>
<br>
// now get only OrderDetails records for the
target Order records<br>
<br>
filterExp =
FilterExpression.CreateInExpressionFromTable&lt;Order&gt;(
&quot;OrderID&quot;, orders, &quot;OrderID&quot; );<br>
IList&lt;OrderDetail&gt; orderDetails =
orderDetailsDb.SelectRecords&lt;OrderDetail&gt;( filterExp );<br>
<br>
// now get only Product records for the
target OrderDetails records<br>
<br>
filterExp =
FilterExpression.CreateInExpressionFromTable&lt;OrderDetail&gt;(
&quot;ProductID&quot;, orderDetails, &quot;ProductID&quot; );<br>
IList&lt;Product&gt; products =
productsDb.SelectRecords&lt;Product&gt;(( filterExp );<br>
<br>
// now we&#39;re ready to do the join<br>
<br>
var query =<br>
&nbsp;&nbsp;&nbsp; from custRec in customers<br>
&nbsp;&nbsp;&nbsp; join orderRec in orders on custRec.CustomerID
equals orderRec.CustomerID<br>
&nbsp;&nbsp;&nbsp; join orderDetailRec in orderDetails on
orderRec.OrderID equals orderDetailRec.OrderID<br>
&nbsp;&nbsp;&nbsp; join productRec in products on
orderDetailRec.ProductID equals productRec.ProductID<br>
&nbsp;&nbsp;&nbsp; select new<br>
&nbsp;&nbsp;&nbsp; {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ID = custRec.CustomerID,<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CompanyName = custRec.CompanyName,<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OrderID = orderRec.OrderID,<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OrderDate = orderRec.OrderDate,<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ProductName = productRec.ProductName,<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; UnitPrice = orderDetailRec.UnitPrice,<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Quantity = orderDetailRec.Quantity<br>
&nbsp;&nbsp;&nbsp; };<br>
<br>
foreach( var rec in query )<br>
{<br>
&nbsp;&nbsp;&nbsp; Debug.WriteLine( rec.ToString() );<br>
}</font></td>
</tr>
</table>
</blockquote>
<p>&nbsp;</p>
</font>
<p><b>
<font color="#0066CC" face="Arial" size="3">Creating a Database</font></p>
</b>
<p><font face="Arial" size="3">You create your database programmatically by defining
Fields and adding them to an array then calling FileDb.Create, similar to below.
Notice we set the ID field to be AutoIncrementing and PrimaryKey. This code
creates a database with every type of field.</font></p>
<font FACE="Courier New">
<blockquote>
<table border="0" width="100%" id="table40" bgcolor="#FBEDBB" cellpadding="10">
<tr>
<td><font size="2" FACE="Courier New">Field field;<br>
var fieldLst = new List&lt;Field&gt;( 20 );<br>
field = new Field( &quot;ID&quot;, DataType.Int );<br>
field.AutoIncStart = 0;<br>
field.IsPrimaryKey = true;<br>
fields.Add( field );<br>
field = new Field( &quot;FirstName&quot;, DataType.String );<br>
fields.Add( field );<br>
field = new Field( &quot;LastName&quot;, DataType.String );<br>
fields.Add( field );<br>
field = new Field( &quot;BirthDate&quot;, DataType.DateTime );<br>
fields.Add( field );<br>
field = new Field( &quot;IsCitizen&quot;, DataType.Bool );<br>
fields.Add( field );<br>
field = new Field( &quot;DoubleField&quot;, DataType.Double );<br>
fields.Add( field );<br>
field = new Field( &quot;ByteField&quot;, DataType.Byte );<br>
fields.Add( field );<br>
<br>
// array types<br>
field = new Field( &quot;StringArrayField&quot;, DataType.String );<br>
field.IsArray = true;<br>
fields.Add( field );<br>
field = new Field( &quot;ByteArrayField&quot;, DataType.Byte );<br>
field.IsArray = true;<br>
fields.Add( field );<br>
field = new Field( &quot;IntArrayField&quot;, DataType.Int );<br>
field.IsArray = true;<br>
fields.Add( field );<br>
field = new Field( &quot;DoubleArrayField&quot;, DataType.Double );<br>
field.IsArray = true;<br>
fields.Add( field );<br>
field = new Field( &quot;DateTimeArrayField&quot;, DataType.DateTime );<br>
field.IsArray = true;<br>
fields.Add( field );<br>
field = new Field( &quot;BoolArray&quot;, DataType.Bool );<br>
field.IsArray = true;<br>
fields.Add( field );</font><font size="2"><br>
<br>
var myDb = new FileDb();</font><font size="2" FACE="Courier New"><br>
myDb.Create( &quot;MyDatabase.fdb&quot;, fieldLst.ToArray() );</font></td>
</tr>
</table>
</blockquote>
</font>
<p><b><font color="#0066CC" face="Arial" size="3"><br>
Adding Records</font></b></p>
<p><font face="Arial" size="3">You add records to a database by creating a FieldValues
object and adding field values. You do not need to represent every field of the
database. Fields that are missing will be initialized to the default value (zero
for numeric types, DateTime.MinValue,
empty for String and NULL for array types).</font></p>
<font FACE="Courier New">
<blockquote>
<table border="0" width="100%" id="table41" bgcolor="#FBEDBB" cellpadding="10">
<tr>
<td>
<font size="2" FACE="Courier New">var record = new FieldValues();<br>
record.Add( &quot;FirstName&quot;, &quot;Nancy&quot; );<br>
record.Add( &quot;LastName&quot;, &quot;Davolio&quot; );<br>
record.Add( &quot;BirthDate&quot;, new DateTime( 1968, 12, 8 ) );<br>
record.Add( &quot;IsCitizen&quot;, true );<br>
record.Add( &quot;Double&quot;, 1.23 );<br>
record.Add( &quot;Byte&quot;, 1 );<br>
record.Add( &quot;StringArray&quot;, new string[] { &quot;s1&quot;, &quot;s2&quot;, &quot;s3&quot; } );<br>
record.Add( &quot;ByteArray&quot;, new Byte[] { 1, 2, 3, 4 } );<br>
record.Add( &quot;IntArray&quot;, new int[] { 100, 200, 300, 400 } );<br>
record.Add( &quot;DoubleArray&quot;, new double[] { 1.2, 2.4, 3.6, 4.8 } );<br>
record.Add( &quot;DateTimeArray&quot;, new DateTime[] { DateTime.Now, DateTime.Now,
DateTime.Now, DateTime.Now } );<br>
record.Add( &quot;BoolArray&quot;, new bool[] { true, false, true, false } );<br>
<br>
myDb.AddRecord( record );</font></td>
</tr>
</table>
</blockquote>
<p>&nbsp;</p>
</font>
</body>
</html>

View file

@ -8,8 +8,8 @@
<ProjectGuid>{EF44F661-B98A-4676-927F-85D138F82300}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Caching</RootNamespace>
<AssemblyName>FileDbCache</AssemblyName>
<RootNamespace>MapControl.Caching</RootNamespace>
<AssemblyName>FileDbCache.WPF</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
@ -40,27 +40,27 @@
<AssemblyOriginatorKeyFile>..\..\MapControl.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="FileDb, Version=3.10.0.0, Culture=neutral, PublicKeyToken=68cc942b9efb3282, processorArchitecture=MSIL">
<Reference Include="FileDb, Version=5.0.1.0, Culture=neutral, PublicKeyToken=ba3f58a0e60cd01d, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>FileDb\FileDb.dll</HintPath>
<HintPath>..\FileDb\FileDb.dll</HintPath>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Runtime.Caching" />
<Reference Include="System.Xaml" />
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="FileDbCache.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="FileDb\FileDb.dll" />
<Content Include="FileDb\FileDb.txt" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\MapControl.snk">
<Link>MapControl.snk</Link>
</None>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View file

@ -7,28 +7,30 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Caching;
using System.Runtime.Serialization.Formatters.Binary;
using System.Windows.Media.Imaging;
using FileDbNs;
namespace Caching
namespace MapControl.Caching
{
/// <summary>
/// ObjectCache implementation based on EzTools FileDb - http://www.eztools-software.com/tools/filedb/.
/// ObjectCache implementation based on FileDb, a free and simple No-SQL database by EzTools Software.
/// See http://www.eztools-software.com/tools/filedb/.
/// The only valid data type for cached values is System.Windows.Media.Imaging.BitmapFrame.
/// </summary>
public class FileDbCache : ObjectCache
public class FileDbCache : ObjectCache, IDisposable
{
private const string keyField = "Key";
private const string valueField = "Value";
private const string expiresField = "Expires";
private readonly BinaryFormatter formatter = new BinaryFormatter();
private readonly FileDb fileDb = new FileDb { AutoFlush = false, AutoCleanThreshold = -1 };
private readonly FileDb fileDb = new FileDb { AutoFlush = true, AutoCleanThreshold = -1 };
private readonly string name;
private readonly string path;
public FileDbCache(string name, NameValueCollection config)
: this(name, config["directory"])
: this(name, config["folder"])
{
var autoFlush = config["autoFlush"];
var autoCleanThreshold = config["autoCleanThreshold"];
@ -58,20 +60,20 @@ namespace Caching
}
}
public FileDbCache(string name, string directory)
public FileDbCache(string name, string folder)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException("The parameter name must not be null or empty or only white-space.");
throw new ArgumentException("The parameter name must not be null or empty or consist only of white-space characters.");
}
if (string.IsNullOrWhiteSpace(directory))
if (string.IsNullOrWhiteSpace(folder))
{
throw new ArgumentException("The parameter directory must not be null or empty or only white-space.");
throw new ArgumentException("The parameter folder must not be null or empty or consist only of white-space characters.");
}
this.name = name;
path = Path.Combine(directory, name.Trim());
path = Path.Combine(folder, name);
if (string.IsNullOrEmpty(Path.GetExtension(path)))
{
@ -81,25 +83,16 @@ namespace Caching
try
{
fileDb.Open(path, false);
Trace.TraceInformation("FileDbCache: Opened database with {0} cached items in {1}", fileDb.NumRecords, path);
Debug.WriteLine("FileDbCache: Opened database with {0} cached items in {1}.", fileDb.NumRecords, path);
fileDb.DeleteRecords(new FilterExpression(expiresField, DateTime.UtcNow, EqualityEnum.LessThan));
if (fileDb.NumDeleted > 0)
{
Trace.TraceInformation("FileDbCache: Deleted {0} expired items", fileDb.NumDeleted);
fileDb.Clean();
}
Clean();
}
catch
{
CreateDatabase();
}
if (fileDb.IsOpen)
{
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
}
AppDomain.CurrentDomain.ProcessExit += (s, e) => Close();
}
public bool AutoFlush
@ -147,50 +140,19 @@ namespace Caching
throw new NotSupportedException("The parameter regionName must be null.");
}
long count = 0;
if (fileDb.IsOpen)
{
try
{
count = fileDb.NumRecords;
return fileDb.NumRecords;
}
catch (Exception ex)
{
Trace.TraceWarning("FileDbCache: FileDb.NumRecords failed: {0}", ex.Message);
if (RepairDatabase())
{
count = fileDb.NumRecords;
}
Debug.WriteLine("FileDbCache: FileDb.NumRecords failed: {0}", (object)ex.Message);
}
}
return count;
}
private Record GetRecord(string key)
{
Record record = null;
if (fileDb.IsOpen)
{
try
{
record = fileDb.GetRecordByKey(key, new string[] { valueField }, false);
}
catch (Exception ex)
{
Trace.TraceWarning("FileDbCache: FileDb.GetRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
if (RepairDatabase())
{
record = fileDb.GetRecordByKey(key, new string[] { valueField }, false);
}
}
}
return record;
return 0;
}
public override bool Contains(string key, string regionName = null)
@ -205,7 +167,19 @@ namespace Caching
throw new NotSupportedException("The parameter regionName must be null.");
}
return GetRecord(key) != null;
if (fileDb.IsOpen)
{
try
{
return fileDb.GetRecordByKey(key, new string[0], false) != null;
}
catch (Exception ex)
{
Debug.WriteLine("FileDbCache: FileDb.GetRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
}
}
return false;
}
public override object Get(string key, string regionName = null)
@ -220,57 +194,57 @@ namespace Caching
throw new NotSupportedException("The parameter regionName must be null.");
}
object value = null;
var record = GetRecord(key);
if (fileDb.IsOpen)
{
Record record = null;
try
{
record = fileDb.GetRecordByKey(key, new string[] { valueField }, false);
}
catch (Exception ex)
{
Debug.WriteLine("FileDbCache: FileDb.GetRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
}
if (record != null)
{
try
{
using (var stream = new MemoryStream((byte[])record[0]))
using (var memoryStream = new MemoryStream((byte[])record[0]))
{
value = formatter.Deserialize(stream);
return BitmapFrame.Create(memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
}
}
catch (Exception ex1)
catch (Exception ex)
{
Trace.TraceWarning("FileDbCache: Deserializing item \"{0}\" failed: {1}", key, ex1.Message);
Debug.WriteLine("FileDbCache: Decoding \"{0}\" failed: {1}", key, ex.Message);
}
try
{
fileDb.DeleteRecordByKey(key);
}
catch (Exception ex2)
catch (Exception ex)
{
Trace.TraceWarning("FileDbCache: FileDb.DeleteRecordByKey(\"{0}\") failed: {1}", key, ex2.Message);
Debug.WriteLine("FileDbCache: FileDb.DeleteRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
}
}
}
return value;
return null;
}
public override CacheItem GetCacheItem(string key, string regionName = null)
{
var value = Get(key, regionName);
return value != null ? new CacheItem(key, value) : null;
}
public override IDictionary<string, object> GetValues(IEnumerable<string> keys, string regionName = null)
{
if (regionName != null)
{
throw new NotSupportedException("The parameter regionName must be null.");
}
var values = new Dictionary<string, object>();
foreach (string key in keys)
{
values[key] = Get(key);
}
return values;
return keys.ToDictionary(key => key, key => Get(key, regionName));
}
public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null)
@ -280,9 +254,9 @@ namespace Caching
throw new ArgumentNullException("The parameter key must not be null.");
}
if (value == null)
if (policy == null)
{
throw new ArgumentNullException("The parameter value must not be null.");
throw new ArgumentNullException("The parameter policy must not be null.");
}
if (regionName != null)
@ -290,24 +264,34 @@ namespace Caching
throw new NotSupportedException("The parameter regionName must be null.");
}
var bitmap = value as BitmapFrame;
if (bitmap == null)
{
throw new ArgumentException("The parameter value must contain a System.Windows.Media.Imaging.BitmapFrame.");
}
if (fileDb.IsOpen)
{
byte[] valueBuffer = null;
byte[] buffer = null;
try
{
using (var stream = new MemoryStream())
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(bitmap);
using (var memoryStream = new MemoryStream())
{
formatter.Serialize(stream, value);
valueBuffer = stream.ToArray();
encoder.Save(memoryStream);
buffer = memoryStream.ToArray();
}
}
catch (Exception ex)
{
Trace.TraceWarning("FileDbCache: Serializing item \"{0}\" failed: {1}", key, ex.Message);
Debug.WriteLine("FileDbCache: Encoding \"{0}\" failed: {1}", key, ex.Message);
}
if (valueBuffer != null)
if (buffer != null)
{
var expires = DateTime.MaxValue;
@ -320,18 +304,9 @@ namespace Caching
expires = DateTime.UtcNow + policy.SlidingExpiration;
}
try
if (!AddOrUpdateRecord(key, buffer, expires) && RepairDatabase())
{
AddOrUpdateRecord(key, valueBuffer, expires);
}
catch (Exception ex)
{
Trace.TraceWarning("FileDbCache: FileDb.UpdateRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
if (RepairDatabase())
{
AddOrUpdateRecord(key, valueBuffer, expires);
}
AddOrUpdateRecord(key, buffer, expires);
}
}
}
@ -350,14 +325,18 @@ namespace Caching
public override object AddOrGetExisting(string key, object value, CacheItemPolicy policy, string regionName = null)
{
var oldValue = Get(key, regionName);
Set(key, value, policy);
return oldValue;
}
public override CacheItem AddOrGetExisting(CacheItem item, CacheItemPolicy policy)
{
var oldItem = GetCacheItem(item.Key, item.RegionName);
Set(item, policy);
return oldItem;
}
@ -378,63 +357,51 @@ namespace Caching
}
catch (Exception ex)
{
Trace.TraceWarning("FileDbCache: FileDb.DeleteRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
Debug.WriteLine("FileDbCache: FileDb.DeleteRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
}
}
return oldValue;
}
public void Flush()
public void Dispose()
{
try
Close();
}
public void Flush()
{
if (fileDb.IsOpen)
{
fileDb.Flush();
}
}
catch (Exception ex)
{
Trace.TraceWarning("FileDbCache: FileDb.Flush() failed: {0}", ex.Message);
}
}
public void Clean()
{
try
{
if (fileDb.IsOpen)
{
fileDb.DeleteRecords(new FilterExpression(expiresField, DateTime.UtcNow, ComparisonOperatorEnum.LessThan));
if (fileDb.NumDeleted > 0)
{
Debug.WriteLine("FileDbCache: Deleted {0} expired items.", fileDb.NumDeleted);
fileDb.Clean();
}
}
catch (Exception ex)
{
Trace.TraceWarning("FileDbCache: FileDb.Clean() failed: {0}", ex.Message);
}
}
public void Close()
private void Close()
{
if (fileDb.IsOpen)
{
fileDb.Close();
AppDomain.CurrentDomain.ProcessExit -= OnProcessExit;
}
}
private void OnProcessExit(object sender, EventArgs e)
{
Close();
}
private void CreateDatabase()
{
if (fileDb.IsOpen)
{
fileDb.Close();
}
Close();
if (File.Exists(path))
{
@ -445,15 +412,14 @@ namespace Caching
Directory.CreateDirectory(Path.GetDirectoryName(path));
}
fileDb.Create(path,
new Field[]
fileDb.Create(path, new Field[]
{
new Field(keyField, DataTypeEnum.String) { IsPrimaryKey = true },
new Field(valueField, DataTypeEnum.Byte) { IsArray = true },
new Field(expiresField, DataTypeEnum.DateTime)
});
Trace.TraceInformation("FileDbCache: Created database {0}", path);
Debug.WriteLine("FileDbCache: Created database {0}.", (object)path);
}
private bool RepairDatabase()
@ -461,40 +427,71 @@ namespace Caching
try
{
fileDb.Reindex();
return true;
}
catch (Exception ex1)
catch (Exception ex)
{
Trace.TraceWarning("FileDbCache: FileDb.Reindex() failed: {0}", ex1.Message);
Debug.WriteLine("FileDbCache: FileDb.Reindex() failed: {0}", (object)ex.Message);
}
try
{
CreateDatabase();
return true;
}
catch (Exception ex2)
catch (Exception ex)
{
Trace.TraceWarning("FileDbCache: Creating database {0} failed: {1}", path, ex2.Message);
Debug.WriteLine("FileDbCache: Creating database {0} failed: {1}", path, ex.Message);
}
return false;
}
private bool AddOrUpdateRecord(string key, byte[] value, DateTime expires)
{
var fieldValues = new FieldValues(3);
fieldValues.Add(valueField, value);
fieldValues.Add(expiresField, expires);
bool recordExists;
try
{
recordExists = fileDb.GetRecordByKey(key, new string[0], false) != null;
}
catch (Exception ex)
{
Debug.WriteLine("FileDbCache: FileDb.GetRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
return false;
}
if (recordExists)
{
try
{
fileDb.UpdateRecordByKey(key, fieldValues);
}
catch (Exception ex)
{
Debug.WriteLine("FileDbCache: FileDb.UpdateRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
return false;
}
}
else
{
try
{
fieldValues.Add(keyField, key);
fileDb.AddRecord(fieldValues);
}
catch (Exception ex)
{
Debug.WriteLine("FileDbCache: FileDb.AddRecord(\"{0}\") failed: {1}", key, ex.Message);
return false;
}
}
return true;
}
private void AddOrUpdateRecord(string key, object value, DateTime expires)
{
var fieldValues = new FieldValues(3); // capacity
fieldValues.Add(valueField, value);
fieldValues.Add(expiresField, expires);
if (fileDb.GetRecordByKey(key, new string[0], false) == null)
{
fieldValues.Add(keyField, key);
fileDb.AddRecord(fieldValues);
}
else
{
fileDb.UpdateRecordByKey(key, fieldValues);
}
}
}
}

View file

@ -1,14 +1,14 @@
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("FileDbCache")]
[assembly: AssemblyTitle("XAML Map Control FileDbCache (WPF)")]
[assembly: AssemblyDescription("ObjectCache implementation based on EzTools FileDb")]
[assembly: AssemblyProduct("XAML Map Control")]
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.3.1")]
[assembly: AssemblyFileVersion("2.3.1")]
[assembly: AssemblyVersion("2.4.0")]
[assembly: AssemblyFileVersion("2.4.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]

View file

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>12.0</MinimumVisualStudioVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{C7BF2B18-CC74-430B-BCB2-600304EFA3D8}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MapControl.Caching</RootNamespace>
<AssemblyName>FileDbCache.WinRT</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetFrameworkProfile>Profile32</TargetFrameworkProfile>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>true</SignAssembly>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>..\..\MapControl.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<TargetPlatform Include="WindowsPhoneApp, Version=8.1" />
<TargetPlatform Include="Windows, Version=8.1" />
</ItemGroup>
<ItemGroup>
<Compile Include="FileDbCache.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\MapControl.snk">
<Link>MapControl.snk</Link>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\MapControl\WinRT\MapControl.WinRT.csproj">
<Project>{63cefdf7-5170-43b6-86f8-5c4a383a1615}</Project>
<Name>MapControl.WinRT</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Reference Include="FileDbPcl">
<HintPath>..\FileDb\FileDbPcl.dll</HintPath>
</Reference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View file

@ -0,0 +1,278 @@
// XAML Map Control - http://xamlmapcontrol.codeplex.com/
// Copyright © 2014 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI.Xaml;
using FileDbNs;
namespace MapControl.Caching
{
/// <summary>
/// IImageCache implementation based on FileDb, a free and simple No-SQL database by EzTools Software.
/// See http://www.eztools-software.com/tools/filedb/.
/// </summary>
public class FileDbCache : IImageCache, IDisposable
{
private const string keyField = "Key";
private const string valueField = "Value";
private const string expiresField = "Expires";
private readonly FileDb fileDb = new FileDb { AutoFlush = true, AutoCleanThreshold = -1 };
private readonly StorageFolder folder;
private readonly string name;
public FileDbCache(string name = null, StorageFolder folder = null)
{
if (string.IsNullOrWhiteSpace(name))
{
name = TileImageLoader.DefaultCacheName;
}
if (string.IsNullOrEmpty(Path.GetExtension(name)))
{
name += ".fdb";
}
if (folder == null)
{
folder = TileImageLoader.DefaultCacheFolder;
}
this.folder = folder;
this.name = name;
Application.Current.Resuming += async (s, e) => await Open();
Application.Current.Suspending += (s, e) => Close();
var task = Open();
}
public void Dispose()
{
Close();
}
public void Clean()
{
if (fileDb.IsOpen)
{
fileDb.DeleteRecords(new FilterExpression(expiresField, DateTime.UtcNow, ComparisonOperatorEnum.LessThan));
if (fileDb.NumDeleted > 0)
{
Debug.WriteLine("FileDbCache: Deleted {0} expired items.", fileDb.NumDeleted);
fileDb.Clean();
}
}
}
public async Task<ImageCacheItem> GetAsync(string key)
{
if (key == null)
{
throw new ArgumentNullException("The parameter key must not be null.");
}
if (!fileDb.IsOpen)
{
return null;
}
return await Task.Run(() => Get(key));
}
public async Task SetAsync(string key, IBuffer buffer, DateTime expires)
{
if (key == null)
{
throw new ArgumentNullException("The parameter key must not be null.");
}
if (buffer == null)
{
throw new ArgumentNullException("The parameter buffer must not be null.");
}
if (fileDb.IsOpen)
{
var bytes = buffer.ToArray();
var ok = await Task.Run(() => AddOrUpdateRecord(key, bytes, expires));
if (!ok && (await RepairDatabase()))
{
await Task.Run(() => AddOrUpdateRecord(key, bytes, expires));
}
}
}
private async Task Open()
{
if (!fileDb.IsOpen)
{
try
{
var file = await folder.GetFileAsync(name);
var stream = await file.OpenAsync(FileAccessMode.ReadWrite);
fileDb.Open(stream.AsStream());
Debug.WriteLine("FileDbCache: Opened database with {0} cached items in {1}.", fileDb.NumRecords, file.Path);
Clean();
return;
}
catch
{
}
await CreateDatabase();
}
}
private void Close()
{
if (fileDb.IsOpen)
{
fileDb.Close();
}
}
private async Task CreateDatabase()
{
Close();
var file = await folder.CreateFileAsync(name, CreationCollisionOption.ReplaceExisting);
var stream = await file.OpenAsync(FileAccessMode.ReadWrite);
fileDb.Create(stream.AsStream(), new Field[]
{
new Field(keyField, DataTypeEnum.String) { IsPrimaryKey = true },
new Field(valueField, DataTypeEnum.Byte) { IsArray = true },
new Field(expiresField, DataTypeEnum.DateTime)
});
Debug.WriteLine("FileDbCache: Created database {0}.", file.Path);
}
private async Task<bool> RepairDatabase()
{
try
{
fileDb.Reindex();
return true;
}
catch (Exception ex)
{
Debug.WriteLine("FileDbCache: FileDb.Reindex() failed: {0}", ex.Message);
}
try
{
await CreateDatabase();
return true;
}
catch (Exception ex)
{
Debug.WriteLine("FileDbCache: Creating database {0} failed: {1}", Path.Combine(folder.Path, name), ex.Message);
}
return false;
}
private ImageCacheItem Get(string key)
{
var fields = new string[] { valueField, expiresField };
Record record = null;
try
{
record = fileDb.GetRecordByKey(key, fields, false);
}
catch (Exception ex)
{
Debug.WriteLine("FileDbCache: FileDb.GetRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
}
if (record != null)
{
try
{
return new ImageCacheItem
{
Buffer = ((byte[])record[0]).AsBuffer(),
Expires = (DateTime)record[1]
};
}
catch (Exception ex)
{
Debug.WriteLine("FileDbCache: Decoding \"{0}\" failed: {1}", key, ex.Message);
}
try
{
fileDb.DeleteRecordByKey(key);
}
catch (Exception ex)
{
Debug.WriteLine("FileDbCache: FileDb.DeleteRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
}
}
return null;
}
private bool AddOrUpdateRecord(string key, byte[] value, DateTime expires)
{
var fieldValues = new FieldValues(3);
fieldValues.Add(valueField, value);
fieldValues.Add(expiresField, expires);
bool recordExists;
try
{
recordExists = fileDb.GetRecordByKey(key, new string[0], false) != null;
}
catch (Exception ex)
{
Debug.WriteLine("FileDbCache: FileDb.GetRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
return false;
}
if (recordExists)
{
try
{
fileDb.UpdateRecordByKey(key, fieldValues);
}
catch (Exception ex)
{
Debug.WriteLine("FileDbCache: FileDb.UpdateRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
return false;
}
}
else
{
try
{
fieldValues.Add(keyField, key);
fileDb.AddRecord(fieldValues);
}
catch (Exception ex)
{
Debug.WriteLine("FileDbCache: FileDb.AddRecord(\"{0}\") failed: {1}", key, ex.Message);
return false;
}
}
//Debug.WriteLine("Cached item {0}, expires {1}", key, expires);
return true;
}
}
}

View file

@ -0,0 +1,14 @@
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("XAML Map Control FileDbCache (WinRT)")]
[assembly: AssemblyDescription("IImageCache implementation based on EzTools FileDb")]
[assembly: AssemblyProduct("XAML Map Control")]
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.4.0")]
[assembly: AssemblyFileVersion("2.4.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]

View file

@ -1,5 +0,0 @@
FileDbCache uses FileDb by Brett Goodman (aka EzTools), a simple No-SQL database for .NET.
FileDb is Open Source on Google Code Hosting at http://code.google.com/p/filedb-database/
and licensed under the Apache License 2.0, http://www.apache.org/licenses/LICENSE-2.0.
See the product homepage at http://www.eztools-software.com/tools/filedb/.
Download FileDb from http://www.eztools-software.com/downloads/filedb.exe.

View file

@ -8,8 +8,8 @@
<ProjectGuid>{86470440-FEE2-4120-AF5A-3762FB9C536F}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Caching</RootNamespace>
<AssemblyName>ImageFileCache</AssemblyName>
<RootNamespace>MapControl.Caching</RootNamespace>
<AssemblyName>ImageFileCache.WPF</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
@ -40,9 +40,12 @@
<AssemblyOriginatorKeyFile>..\..\MapControl.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="PresentationCore" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Runtime.Caching" />
<Reference Include="System.Xaml" />
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="ImageFileCache.cs" />

View file

@ -6,61 +6,52 @@ using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.Caching;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Windows.Media.Imaging;
namespace Caching
namespace MapControl.Caching
{
/// <summary>
/// ObjectCache implementation based on local image files.
/// The only valid data type for cached values is a byte array containing an
/// 8-byte timestamp followed by a PNG, JPEG, BMP, GIF, TIFF or WMP image buffer.
/// The only valid data type for cached values is System.Windows.Media.Imaging.BitmapFrame.
/// </summary>
public class ImageFileCache : ObjectCache
{
private static readonly Tuple<string, byte[]>[] imageFileTypes = new Tuple<string, byte[]>[]
{
new Tuple<string, byte[]>(".png", new byte[] { 0x89, 0x50, 0x4E, 0x47, 0xD, 0xA, 0x1A, 0xA }),
new Tuple<string, byte[]>(".jpg", new byte[] { 0xFF, 0xD8, 0xFF, 0xE0, 0, 0x10, 0x4A, 0x46, 0x49, 0x46, 0 }),
new Tuple<string, byte[]>(".bmp", new byte[] { 0x42, 0x4D }),
new Tuple<string, byte[]>(".gif", new byte[] { 0x47, 0x49, 0x46 }),
new Tuple<string, byte[]>(".tif", new byte[] { 0x49, 0x49, 42, 0 }),
new Tuple<string, byte[]>(".tif", new byte[] { 0x4D, 0x4D, 0, 42 }),
new Tuple<string, byte[]>(".wdp", new byte[] { 0x49, 0x49, 0xBC }),
};
private static readonly FileSystemAccessRule fullControlRule = new FileSystemAccessRule(
new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null),
FileSystemRights.FullControl, AccessControlType.Allow);
private readonly MemoryCache memoryCache = MemoryCache.Default;
private readonly string name;
private readonly string directory;
private readonly string rootFolder;
public ImageFileCache(string name, NameValueCollection config)
: this(name, config["directory"])
: this(name, config["folder"])
{
}
public ImageFileCache(string name, string directory)
public ImageFileCache(string name, string folder)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException("The parameter name must not be null or empty or only white-space.");
throw new ArgumentException("The parameter name must not be null or empty or consist only of white-space characters.");
}
if (string.IsNullOrWhiteSpace(directory))
if (string.IsNullOrWhiteSpace(folder))
{
throw new ArgumentException("The parameter directory must not be null or empty or only white-space.");
throw new ArgumentException("The parameter folder must not be null or empty or consist only of white-space characters.");
}
this.name = name;
this.directory = Path.Combine(directory, name.Trim());
Directory.CreateDirectory(this.directory);
rootFolder = Path.Combine(folder, name);
Directory.CreateDirectory(rootFolder);
Trace.TraceInformation("Created ImageFileCache in {0}", this.directory);
Debug.WriteLine("Created ImageFileCache in {0}.", (object)rootFolder);
}
public override string Name
@ -81,46 +72,29 @@ namespace Caching
protected override IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
throw new NotSupportedException("LocalFileCache does not support the ability to enumerate items.");
throw new NotSupportedException("ImageFileCache does not support the ability to enumerate items.");
}
public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(IEnumerable<string> keys, string regionName = null)
{
throw new NotSupportedException("LocalFileCache does not support the ability to create change monitors.");
throw new NotSupportedException("ImageFileCache does not support the ability to create change monitors.");
}
public override long GetCount(string regionName = null)
{
throw new NotSupportedException("LocalFileCache does not support the ability to count items.");
throw new NotSupportedException("ImageFileCache does not support the ability to count items.");
}
public override bool Contains(string key, string regionName = null)
{
if (regionName != null)
{
throw new NotSupportedException("The parameter regionName must be null.");
}
try
{
return MemoryCache.Default.Contains(key) || FindFile(GetPath(key)) != null;
}
catch
{
return false;
}
return memoryCache.Contains(key, regionName) || FindFile(GetPath(key)) != null;
}
public override object Get(string key, string regionName = null)
{
if (regionName != null)
{
throw new NotSupportedException("The parameter regionName must be null.");
}
var bitmap = memoryCache.Get(key, regionName) as BitmapFrame;
var value = MemoryCache.Default.Get(key);
if (value == null)
if (bitmap == null)
{
try
{
@ -128,20 +102,20 @@ namespace Caching
if (path != null)
{
var expirationTime = File.GetLastAccessTimeUtc(path);
var creationTime = File.GetLastWriteTimeUtc(path);
if (expirationTime < creationTime)
using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
expirationTime = creationTime;
bitmap = BitmapFrame.Create(fileStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
var metadata = (BitmapMetadata)bitmap.Metadata;
DateTime expiration;
// metadata.DateTaken must be parsed in CurrentCulture
if (metadata != null &&
metadata.DateTaken != null &&
DateTime.TryParse(metadata.DateTaken, CultureInfo.CurrentCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out expiration))
{
memoryCache.Set(key, bitmap, expiration, regionName);
}
using (var fileStream = new FileStream(path, FileMode.Open))
using (var memoryStream = new MemoryStream((int)(fileStream.Length + 8)))
{
memoryStream.Write(BitConverter.GetBytes(expirationTime.ToBinary()), 0, 8);
fileStream.CopyTo(memoryStream);
value = memoryStream.GetBuffer();
}
}
}
@ -150,7 +124,7 @@ namespace Caching
}
}
return value;
return bitmap;
}
public override CacheItem GetCacheItem(string key, string regionName = null)
@ -162,58 +136,72 @@ namespace Caching
public override IDictionary<string, object> GetValues(IEnumerable<string> keys, string regionName = null)
{
if (regionName != null)
{
throw new NotSupportedException("The parameter regionName must be null.");
}
var values = new Dictionary<string, object>();
foreach (string key in keys)
{
values[key] = Get(key);
}
return values;
return keys.ToDictionary(key => key, key => Get(key, regionName));
}
public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null)
{
if (regionName != null)
var bitmap = value as BitmapFrame;
if (bitmap == null)
{
throw new NotSupportedException("The parameter regionName must be null.");
throw new ArgumentException("The parameter value must contain a System.Windows.Media.Imaging.BitmapFrame.");
}
var buffer = value as byte[];
var metadata = (BitmapMetadata)bitmap.Metadata;
var format = metadata != null ? metadata.Format : "bmp";
BitmapEncoder encoder = null;
if (buffer == null || buffer.Length <= 8)
switch (format)
{
throw new NotSupportedException("The parameter value must be a byte[] containing at least 9 bytes.");
case "bmp":
encoder = new BmpBitmapEncoder();
break;
case "gif":
encoder = new GifBitmapEncoder();
break;
case "jpg":
encoder = new JpegBitmapEncoder();
break;
case "png":
encoder = new PngBitmapEncoder();
break;
case "tiff":
encoder = new TiffBitmapEncoder();
break;
case "wmphoto":
encoder = new WmpBitmapEncoder();
break;
default:
break;
}
MemoryCache.Default.Set(key, buffer, policy);
if (encoder == null)
{
throw new NotSupportedException(string.Format("The bitmap format {0} is not supported.", format));
}
var path = GetPath(key) + GetFileExtension(buffer);
memoryCache.Set(key, bitmap, policy, regionName);
var path = string.Format("{0}.{1}", GetPath(key), format);
try
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
using (var fileStream = new FileStream(path, FileMode.Create))
using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write))
{
fileStream.Write(buffer, 8, buffer.Length - 8);
encoder.Frames.Add(bitmap);
encoder.Save(fileStream);
}
var expirationTime = DateTime.FromBinary(BitConverter.ToInt64(buffer, 0));
File.SetLastAccessTimeUtc(path, expirationTime);
var fileSecurity = File.GetAccessControl(path);
fileSecurity.AddAccessRule(fullControlRule);
File.SetAccessControl(path, fileSecurity);
}
catch (Exception ex)
{
Trace.TraceWarning("ImageFileCache: Writing file {0} failed: {1}", path, ex.Message);
Debug.WriteLine("ImageFileCache: Writing file {0} failed: {1}", path, ex.Message);
}
}
@ -253,7 +241,8 @@ namespace Caching
public override object Remove(string key, string regionName = null)
{
var oldValue = Get(key, regionName);
MemoryCache.Default.Remove(key);
memoryCache.Remove(key, regionName);
try
{
@ -273,7 +262,7 @@ namespace Caching
private string GetPath(string key)
{
return Path.Combine(directory, key);
return Path.Combine(rootFolder, key);
}
private static string FindFile(string path)
@ -283,34 +272,14 @@ namespace Caching
return path;
}
string directoryName = Path.GetDirectoryName(path);
string folderName = Path.GetDirectoryName(path);
if (Directory.Exists(directoryName))
if (Directory.Exists(folderName))
{
return Directory.EnumerateFiles(directoryName, Path.GetFileName(path) + ".*").FirstOrDefault();
return Directory.EnumerateFiles(folderName, Path.GetFileName(path) + ".*").FirstOrDefault();
}
return null;
}
private static string GetFileExtension(byte[] buffer)
{
var fileType = imageFileTypes.FirstOrDefault(t =>
{
int i = 0;
if (t.Item2.Length <= buffer.Length - 8)
{
while (i < t.Item2.Length && t.Item2[i] == buffer[i + 8])
{
i++;
}
}
return i == t.Item2.Length;
});
return fileType != null ? fileType.Item1 : ".bin";
}
}
}

View file

@ -1,14 +1,14 @@
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("ImageFileCache")]
[assembly: AssemblyTitle("XAML Map Control ImageFileCache (WPF)")]
[assembly: AssemblyDescription("ObjectCache implementation based on local image files")]
[assembly: AssemblyProduct("XAML Map Control")]
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.3.1")]
[assembly: AssemblyFileVersion("2.3.1")]
[assembly: AssemblyVersion("2.4.0")]
[assembly: AssemblyFileVersion("2.4.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]

View file

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>12.0</MinimumVisualStudioVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{F789647E-96F7-43E3-A895-FA3FE8D01260}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MapControl.Caching</RootNamespace>
<AssemblyName>ImageFileCache.WinRT</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{BC8A1FFA-BEE3-4634-8014-F334798102B3};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetPlatformIdentifier>Windows</TargetPlatformIdentifier>
<TargetPlatformVersion>8.1</TargetPlatformVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_APP</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_APP</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>true</SignAssembly>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>..\..\MapControl.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<!-- A reference to the entire .NET Framework is automatically included -->
<None Include="..\..\MapControl.snk">
<Link>MapControl.snk</Link>
</None>
</ItemGroup>
<ItemGroup>
<Compile Include="ImageFileCache.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\MapControl\WinRT\MapControl.WinRT.csproj">
<Project>{63cefdf7-5170-43b6-86f8-5c4a383a1615}</Project>
<Name>MapControl.WinRT</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View file

@ -0,0 +1,94 @@
// XAML Map Control - http://xamlmapcontrol.codeplex.com/
// Copyright © 2014 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.Storage.Streams;
namespace MapControl.Caching
{
public class ImageFileCache : IImageCache
{
private readonly string name;
private StorageFolder rootFolder;
public ImageFileCache(string name = null, StorageFolder folder = null)
{
if (string.IsNullOrWhiteSpace(name))
{
name = TileImageLoader.DefaultCacheName;
}
if (folder == null)
{
folder = TileImageLoader.DefaultCacheFolder;
}
this.name = name;
folder.CreateFolderAsync(name, CreationCollisionOption.OpenIfExists).Completed = (o, s) =>
{
rootFolder = o.GetResults();
Debug.WriteLine("Created ImageFileCache in {0}.", rootFolder.Path);
};
}
public virtual async Task<ImageCacheItem> GetAsync(string key)
{
var item = await rootFolder.TryGetItemAsync(key);
if (item == null || !item.IsOfType(StorageItemTypes.File))
{
return null;
}
var file = (StorageFile)item;
var cacheItem = new ImageCacheItem
{
Buffer = await FileIO.ReadBufferAsync(file),
};
try
{
// Use ImageProperties.DateTaken to get expiration date
var imageProperties = await file.Properties.GetImagePropertiesAsync();
cacheItem.Expires = imageProperties.DateTaken.UtcDateTime;
}
catch
{
}
return cacheItem;
}
public virtual async Task SetAsync(string key, IBuffer buffer, DateTime expires)
{
try
{
var names = key.Split('\\');
var folder = rootFolder;
for (int i = 0; i < names.Length - 1; i++)
{
folder = await folder.CreateFolderAsync(names[i], CreationCollisionOption.OpenIfExists);
}
var file = await folder.CreateFileAsync(names[names.Length - 1], CreationCollisionOption.ReplaceExisting);
await FileIO.WriteBufferAsync(file, buffer);
// Use ImageProperties.DateTaken to store expiration date
var imageProperties = await file.Properties.GetImagePropertiesAsync();
imageProperties.DateTaken = expires;
await imageProperties.SavePropertiesAsync();
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
}
}

View file

@ -0,0 +1,14 @@
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("XAML Map Control ImageFileCache (WinRT)")]
[assembly: AssemblyDescription("IImageCache implementation based on local image files")]
[assembly: AssemblyProduct("XAML Map Control")]
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.4.0")]
[assembly: AssemblyFileVersion("2.4.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]

View file

@ -1,44 +1,72 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.30723.0
VisualStudioVersion = 12.0.31101.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileDbCache", "Caching\FileDbCache\FileDbCache.csproj", "{EF44F661-B98A-4676-927F-85D138F82300}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileDbCache.WinRT", "Caching\FileDbCache.WinRT\FileDbCache.WinRT.csproj", "{C7BF2B18-CC74-430B-BCB2-600304EFA3D8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageFileCache", "Caching\ImageFileCache\ImageFileCache.csproj", "{86470440-FEE2-4120-AF5A-3762FB9C536F}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileDbCache.WPF", "Caching\FileDbCache.WPF\FileDbCache.WPF.csproj", "{EF44F661-B98A-4676-927F-85D138F82300}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageFileCache.WinRT", "Caching\ImageFileCache.WinRT\ImageFileCache.WinRT.csproj", "{F789647E-96F7-43E3-A895-FA3FE8D01260}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageFileCache.WPF", "Caching\ImageFileCache.WPF\ImageFileCache.WPF.csproj", "{86470440-FEE2-4120-AF5A-3762FB9C536F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.Silverlight", "MapControl\MapControl.Silverlight.csproj", "{EB133B78-DEFF-416A-8F0C-89E54D766576}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.WinRT", "MapControl\WinRT\MapControl.WinRT.csproj", "{63CEFDF7-5170-43B6-86F8-5C4A383A1615}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.WPF", "MapControl\MapControl.WPF.csproj", "{226F3575-B683-446D-A2F0-181291DC8787}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhoneApplication", "SampleApps\PhoneApplication\PhoneApplication.csproj", "{8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SilverlightApplication", "SampleApps\SilverlightApplication\SilverlightApplication.csproj", "{CBA8C535-CCA3-4F60-8D3E-0E25791CBD21}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SilverlightApplication.Web", "SampleApps\SilverlightApplication.Web\SilverlightApplication.Web.csproj", "{177C4EF8-0B0A-426E-BDCC-168DC10AC1C1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.WPF", "MapControl\MapControl.WPF.csproj", "{226F3575-B683-446D-A2F0-181291DC8787}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StoreApplication", "SampleApps\StoreApplication\StoreApplication.csproj", "{747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.WinRT", "MapControl\WinRT\MapControl.WinRT.csproj", "{63CEFDF7-5170-43B6-86F8-5C4A383A1615}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfApplication", "SampleApps\WpfApplication\WpfApplication.csproj", "{9949326E-9261-4F95-89B1-151F60498951}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhoneApplication", "SampleApps\PhoneApplication\PhoneApplication.csproj", "{8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.Silverlight", "MapControl\MapControl.Silverlight.csproj", "{EB133B78-DEFF-416A-8F0C-89E54D766576}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.PhoneSilverlight", "MapControl\MapControl.PhoneSilverlight.csproj", "{3499D618-2846-4FCE-A418-7D211FDBDCB3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C7BF2B18-CC74-430B-BCB2-600304EFA3D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C7BF2B18-CC74-430B-BCB2-600304EFA3D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C7BF2B18-CC74-430B-BCB2-600304EFA3D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C7BF2B18-CC74-430B-BCB2-600304EFA3D8}.Release|Any CPU.Build.0 = Release|Any CPU
{EF44F661-B98A-4676-927F-85D138F82300}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EF44F661-B98A-4676-927F-85D138F82300}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EF44F661-B98A-4676-927F-85D138F82300}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EF44F661-B98A-4676-927F-85D138F82300}.Release|Any CPU.Build.0 = Release|Any CPU
{F789647E-96F7-43E3-A895-FA3FE8D01260}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F789647E-96F7-43E3-A895-FA3FE8D01260}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F789647E-96F7-43E3-A895-FA3FE8D01260}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F789647E-96F7-43E3-A895-FA3FE8D01260}.Release|Any CPU.Build.0 = Release|Any CPU
{86470440-FEE2-4120-AF5A-3762FB9C536F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{86470440-FEE2-4120-AF5A-3762FB9C536F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{86470440-FEE2-4120-AF5A-3762FB9C536F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{86470440-FEE2-4120-AF5A-3762FB9C536F}.Release|Any CPU.Build.0 = Release|Any CPU
{EB133B78-DEFF-416A-8F0C-89E54D766576}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EB133B78-DEFF-416A-8F0C-89E54D766576}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB133B78-DEFF-416A-8F0C-89E54D766576}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB133B78-DEFF-416A-8F0C-89E54D766576}.Release|Any CPU.Build.0 = Release|Any CPU
{63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Debug|Any CPU.Build.0 = Debug|Any CPU
{63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Release|Any CPU.ActiveCfg = Release|Any CPU
{63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Release|Any CPU.Build.0 = Release|Any CPU
{226F3575-B683-446D-A2F0-181291DC8787}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{226F3575-B683-446D-A2F0-181291DC8787}.Debug|Any CPU.Build.0 = Debug|Any CPU
{226F3575-B683-446D-A2F0-181291DC8787}.Release|Any CPU.ActiveCfg = Release|Any CPU
{226F3575-B683-446D-A2F0-181291DC8787}.Release|Any CPU.Build.0 = Release|Any CPU
{8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Release|Any CPU.Build.0 = Release|Any CPU
{8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Release|Any CPU.Deploy.0 = Release|Any CPU
{CBA8C535-CCA3-4F60-8D3E-0E25791CBD21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CBA8C535-CCA3-4F60-8D3E-0E25791CBD21}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CBA8C535-CCA3-4F60-8D3E-0E25791CBD21}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -47,38 +75,16 @@ Global
{177C4EF8-0B0A-426E-BDCC-168DC10AC1C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{177C4EF8-0B0A-426E-BDCC-168DC10AC1C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{177C4EF8-0B0A-426E-BDCC-168DC10AC1C1}.Release|Any CPU.Build.0 = Release|Any CPU
{226F3575-B683-446D-A2F0-181291DC8787}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{226F3575-B683-446D-A2F0-181291DC8787}.Debug|Any CPU.Build.0 = Debug|Any CPU
{226F3575-B683-446D-A2F0-181291DC8787}.Release|Any CPU.ActiveCfg = Release|Any CPU
{226F3575-B683-446D-A2F0-181291DC8787}.Release|Any CPU.Build.0 = Release|Any CPU
{747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}.Release|Any CPU.Build.0 = Release|Any CPU
{747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}.Release|Any CPU.Deploy.0 = Release|Any CPU
{63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Debug|Any CPU.Build.0 = Debug|Any CPU
{63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Release|Any CPU.ActiveCfg = Release|Any CPU
{63CEFDF7-5170-43B6-86F8-5C4A383A1615}.Release|Any CPU.Build.0 = Release|Any CPU
{9949326E-9261-4F95-89B1-151F60498951}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9949326E-9261-4F95-89B1-151F60498951}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9949326E-9261-4F95-89B1-151F60498951}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9949326E-9261-4F95-89B1-151F60498951}.Release|Any CPU.Build.0 = Release|Any CPU
{8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Release|Any CPU.Build.0 = Release|Any CPU
{8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Release|Any CPU.Deploy.0 = Release|Any CPU
{EB133B78-DEFF-416A-8F0C-89E54D766576}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EB133B78-DEFF-416A-8F0C-89E54D766576}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB133B78-DEFF-416A-8F0C-89E54D766576}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB133B78-DEFF-416A-8F0C-89E54D766576}.Release|Any CPU.Build.0 = Release|Any CPU
{3499D618-2846-4FCE-A418-7D211FDBDCB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3499D618-2846-4FCE-A418-7D211FDBDCB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3499D618-2846-4FCE-A418-7D211FDBDCB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3499D618-2846-4FCE-A418-7D211FDBDCB3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View file

@ -57,8 +57,7 @@ namespace MapControl
var request = (HttpWebRequest)asyncResult.AsyncState;
using (var response = request.EndGetResponse(asyncResult))
using (var responseStream = response.GetResponseStream())
using (var xmlReader = XmlReader.Create(responseStream))
using (var xmlReader = XmlReader.Create(response.GetResponseStream()))
{
ReadImageryMetadataResponse(xmlReader);
}

View file

@ -21,7 +21,7 @@ namespace MapControl
/// Converts text containing hyperlinks in markdown syntax [text](url)
/// to a collection of Run and Hyperlink inlines.
/// </summary>
public static ICollection<Inline> ToInlines(this string text)
public static List<Inline> ToInlines(this string text)
{
var inlines = new List<Inline>();

View file

@ -6,8 +6,6 @@ namespace MapControl
{
public interface IMapElement
{
MapBase ParentMap { get; }
void SetParentMap(MapBase parentMap);
MapBase ParentMap { get; set; }
}
}

View file

@ -0,0 +1,14 @@
// XAML Map Control - http://xamlmapcontrol.codeplex.com/
// Copyright © 2014 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System.Collections.Generic;
namespace MapControl
{
public interface ITileImageLoader
{
void BeginLoadTiles(TileLayer tileLayer, IEnumerable<Tile> tiles);
void CancelLoadTiles(TileLayer tileLayer);
}
}

View file

@ -0,0 +1,22 @@
// XAML Map Control - http://xamlmapcontrol.codeplex.com/
// Copyright © 2014 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Threading.Tasks;
using Windows.Storage.Streams;
namespace MapControl.Caching
{
public class ImageCacheItem
{
public IBuffer Buffer { get; set; }
public DateTime Expires { get; set; }
}
public interface IImageCache
{
Task<ImageCacheItem> GetAsync(string key);
Task SetAsync(string key, IBuffer buffer, DateTime expires);
}
}

View file

@ -1,67 +0,0 @@
// XAML Map Control - http://xamlmapcontrol.codeplex.com/
// Copyright © 2014 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.Storage.Streams;
namespace MapControl
{
public class ImageFileCache : IObjectCache
{
private readonly IStorageFolder rootFolder;
public ImageFileCache()
{
rootFolder = ApplicationData.Current.TemporaryFolder;
}
public ImageFileCache(IStorageFolder folder)
{
if (folder == null)
{
throw new ArgumentNullException("The parameter folder must not be null.");
}
rootFolder = folder;
}
public async Task<object> GetAsync(string key)
{
try
{
return await PathIO.ReadBufferAsync(Path.Combine(rootFolder.Path, key));
}
catch
{
return null;
}
}
public async Task SetAsync(string key, object value)
{
try
{
var buffer = (IBuffer)value;
var names = key.Split('\\');
var folder = rootFolder;
for (int i = 0; i < names.Length - 1; i++)
{
folder = await folder.CreateFolderAsync(names[i], CreationCollisionOption.OpenIfExists);
}
var file = await folder.CreateFileAsync(names[names.Length - 1], CreationCollisionOption.ReplaceExisting);
await FileIO.WriteBufferAsync(file, buffer);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
}
}

View file

@ -30,15 +30,16 @@ namespace MapControl
{
if (IsAsync)
{
var buffer = new WebClient().DownloadData(uri);
var request = HttpWebRequest.CreateHttp(uri);
request.UserAgent = TileImageLoader.HttpUserAgent;
if (buffer != null)
using (var response = (HttpWebResponse)request.GetResponse())
using (var responseStream = response.GetResponseStream())
using (var memoryStream = new MemoryStream())
{
using (var stream = new MemoryStream(buffer))
{
image = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
image.Freeze();
}
responseStream.CopyTo(memoryStream);
memoryStream.Position = 0;
image = BitmapFrame.Create(memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
}
}
else

View file

@ -4,7 +4,7 @@
namespace MapControl
{
internal struct Int32Rect
public struct Int32Rect
{
public Int32Rect(int x, int y, int width, int height)
: this()

View file

@ -2,14 +2,17 @@
// Copyright © 2014 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
#if WINDOWS_RUNTIME
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Controls;
#else
using System.Windows;
using System.Windows.Media;
using System.Windows.Controls;
#endif
namespace MapControl
@ -45,7 +48,11 @@ namespace MapControl
partial void Initialize()
{
Background = new SolidColorBrush(Colors.Transparent);
// set Background by Style to enable resetting by ClearValue in RemoveTileLayers
var style = new Style(typeof(MapBase));
style.Setters.Add(new Setter(Panel.BackgroundProperty, new SolidColorBrush(Colors.Transparent)));
Style = style;
Clip = new RectangleGeometry();
SizeChanged += OnRenderSizeChanged;
@ -58,11 +65,39 @@ namespace MapControl
UpdateTransform();
}
private void SetViewportTransform(Point mapOrigin)
{
viewportTransform.Matrix = Matrix.Identity
.Translate(-mapOrigin.X, -mapOrigin.Y)
.Scale(ViewportScale, -ViewportScale)
.Rotate(Heading)
.Translate(viewportOrigin.X, viewportOrigin.Y);
}
private void SetTileLayerTransform()
{
var scale = Math.Pow(2d, ZoomLevel - TileZoomLevel);
tileLayerTransform.Matrix = Matrix.Identity
.Translate(TileGrid.X * TileSource.TileSize, TileGrid.Y * TileSource.TileSize)
.Scale(scale, scale)
.Translate(tileLayerOffset.X, tileLayerOffset.Y)
.RotateAt(Heading, viewportOrigin.X, viewportOrigin.Y); ;
}
private void SetTransformMatrixes()
{
scaleTransform.Matrix = new Matrix(CenterScale, 0d, 0d, CenterScale, 0d, 0d);
scaleTransform.Matrix = Matrix.Identity.Scale(CenterScale, CenterScale);
rotateTransform.Matrix = Matrix.Identity.Rotate(Heading);
scaleRotateTransform.Matrix = scaleTransform.Matrix.Multiply(rotateTransform.Matrix);
}
private Matrix GetTileIndexMatrix(double scale)
{
return viewportTransform.Matrix
.Invert() // view to map coordinates
.Translate(180d, -180d)
.Scale(scale, -scale); // map coordinates to tile indices
}
}
}

View file

@ -2,6 +2,7 @@
// Copyright © 2014 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
@ -11,7 +12,7 @@ namespace MapControl
public partial class MapBase
{
public static readonly DependencyProperty ForegroundProperty =
System.Windows.Controls.Control.ForegroundProperty.AddOwner(typeof(MapBase));
Control.ForegroundProperty.AddOwner(typeof(MapBase));
public static readonly DependencyProperty CenterProperty = DependencyProperty.Register(
"Center", typeof(Location), typeof(MapBase), new FrameworkPropertyMetadata(
@ -64,13 +65,50 @@ namespace MapControl
UpdateTransform();
}
private void SetViewportTransform(Point mapOrigin)
{
var transform = Matrix.Identity;
transform.Translate(-mapOrigin.X, -mapOrigin.Y);
transform.Scale(ViewportScale, -ViewportScale);
transform.Rotate(Heading);
transform.Translate(viewportOrigin.X, viewportOrigin.Y);
viewportTransform.Matrix = transform;
}
private void SetTileLayerTransform()
{
var scale = Math.Pow(2d, ZoomLevel - TileZoomLevel);
var transform = Matrix.Identity;
transform.Translate(TileGrid.X * TileSource.TileSize, TileGrid.Y * TileSource.TileSize);
transform.Scale(scale, scale);
transform.Translate(tileLayerOffset.X, tileLayerOffset.Y);
transform.RotateAt(Heading, viewportOrigin.X, viewportOrigin.Y);
tileLayerTransform.Matrix = transform;
}
private void SetTransformMatrixes()
{
Matrix rotateMatrix = Matrix.Identity;
var rotateMatrix = Matrix.Identity;
rotateMatrix.Rotate(Heading);
rotateTransform.Matrix = rotateMatrix;
scaleTransform.Matrix = new Matrix(CenterScale, 0d, 0d, CenterScale, 0d, 0d);
scaleRotateTransform.Matrix = scaleTransform.Matrix * rotateMatrix;
var scaleMatrix = Matrix.Identity;
scaleMatrix.Scale(CenterScale, CenterScale);
scaleTransform.Matrix = scaleMatrix;
scaleRotateTransform.Matrix = scaleMatrix * rotateMatrix;
}
private Matrix GetTileIndexMatrix(double scale)
{
var transform = viewportTransform.Matrix;
transform.Invert(); // view to map coordinates
transform.Translate(180d, -180d);
transform.Scale(scale, -scale); // map coordinates to tile indices
return transform;
}
}
}

View file

@ -3,6 +3,7 @@
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
#if WINDOWS_RUNTIME
@ -14,6 +15,7 @@ using Windows.UI.Xaml.Media.Animation;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
#endif
namespace MapControl
@ -28,17 +30,17 @@ namespace MapControl
{
private const double MaximumZoomLevel = 22d;
public static readonly DependencyProperty TileLayersProperty = DependencyProperty.Register(
"TileLayers", typeof(TileLayerCollection), typeof(MapBase), new PropertyMetadata(null,
(o, e) => ((MapBase)o).TileLayersPropertyChanged((TileLayerCollection)e.OldValue, (TileLayerCollection)e.NewValue)));
public static TimeSpan TileUpdateInterval = TimeSpan.FromSeconds(0.5);
public static TimeSpan AnimationDuration = TimeSpan.FromSeconds(0.3);
public static EasingFunctionBase AnimationEasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut };
public static readonly DependencyProperty TileLayerProperty = DependencyProperty.Register(
"TileLayer", typeof(TileLayer), typeof(MapBase), new PropertyMetadata(null,
(o, e) => ((MapBase)o).TileLayerPropertyChanged((TileLayer)e.NewValue)));
public static readonly DependencyProperty TileOpacityProperty = DependencyProperty.Register(
"TileOpacity", typeof(double), typeof(MapBase), new PropertyMetadata(1d,
(o, e) => ((MapBase)o).tileContainer.Opacity = (double)e.NewValue));
public static readonly DependencyProperty TileLayersProperty = DependencyProperty.Register(
"TileLayers", typeof(TileLayerCollection), typeof(MapBase), new PropertyMetadata(null,
(o, e) => ((MapBase)o).TileLayersPropertyChanged((TileLayerCollection)e.OldValue, (TileLayerCollection)e.NewValue)));
public static readonly DependencyProperty MinZoomLevelProperty = DependencyProperty.Register(
"MinZoomLevel", typeof(double), typeof(MapBase), new PropertyMetadata(1d,
@ -52,29 +54,32 @@ namespace MapControl
"CenterPoint", typeof(Point), typeof(MapBase), new PropertyMetadata(new Point(),
(o, e) => ((MapBase)o).CenterPointPropertyChanged((Point)e.NewValue)));
private readonly TileContainer tileContainer = new TileContainer();
private readonly PanelBase tileLayerPanel = new PanelBase();
private readonly DispatcherTimer tileUpdateTimer = new DispatcherTimer { Interval = TileUpdateInterval };
private readonly MapTransform mapTransform = new MercatorTransform();
private readonly MatrixTransform viewportTransform = new MatrixTransform();
private readonly MatrixTransform tileLayerTransform = new MatrixTransform();
private readonly MatrixTransform scaleTransform = new MatrixTransform();
private readonly MatrixTransform rotateTransform = new MatrixTransform();
private readonly MatrixTransform scaleRotateTransform = new MatrixTransform();
private Location transformOrigin;
private Point viewportOrigin;
private Point tileLayerOffset;
private PointAnimation centerAnimation;
private DoubleAnimation zoomLevelAnimation;
private DoubleAnimation headingAnimation;
private Brush storedBackground;
private Brush storedForeground;
private bool internalPropertyChange;
public MapBase()
{
SetParentMap();
Children.Add(tileLayerPanel);
TileLayers = new TileLayerCollection();
Children.Add(tileContainer);
Initialize();
tileUpdateTimer.Tick += UpdateTiles;
Loaded += OnLoaded;
Initialize();
}
partial void Initialize();
@ -85,6 +90,11 @@ namespace MapControl
/// </summary>
public event EventHandler ViewportChanged;
/// <summary>
/// Raised when the TileZoomLevel or TileGrid properties have changed.
/// </summary>
public event EventHandler TileGridChanged;
/// <summary>
/// Gets or sets the map foreground Brush.
/// </summary>
@ -95,16 +105,7 @@ namespace MapControl
}
/// <summary>
/// Gets or sets the TileLayers used by this Map.
/// </summary>
public TileLayerCollection TileLayers
{
get { return (TileLayerCollection)GetValue(TileLayersProperty); }
set { SetValue(TileLayersProperty, value); }
}
/// <summary>
/// Gets or sets the base TileLayer used by this Map, i.e. TileLayers[0].
/// Gets or sets the base TileLayer used by the Map control.
/// </summary>
public TileLayer TileLayer
{
@ -113,12 +114,15 @@ namespace MapControl
}
/// <summary>
/// Gets or sets the opacity of the tile layers.
/// Gets or sets optional multiple TileLayers that are used simultaneously.
/// The first element in the collection is equal to the value of the TileLayer property.
/// The additional TileLayers usually have transparent backgrounds and their IsOverlay
/// property is set to true.
/// </summary>
public double TileOpacity
public TileLayerCollection TileLayers
{
get { return (double)GetValue(TileOpacityProperty); }
set { SetValue(TileOpacityProperty, value); }
get { return (TileLayerCollection)GetValue(TileLayersProperty); }
set { SetValue(TileLayersProperty, value); }
}
/// <summary>
@ -208,7 +212,15 @@ namespace MapControl
/// </summary>
public Transform ViewportTransform
{
get { return tileContainer.ViewportTransform; }
get { return viewportTransform; }
}
/// <summary>
/// Gets the RenderTransform to be used by TileLayers, with origin at TileGrid.X and TileGrid.Y.
/// </summary>
public Transform TileLayerTransform
{
get { return tileLayerTransform; }
}
/// <summary>
@ -245,6 +257,16 @@ namespace MapControl
/// </summary>
public double CenterScale { get; private set; }
/// <summary>
/// Gets the zoom level to be used by TileLayers.
/// </summary>
public int TileZoomLevel { get; private set; }
/// <summary>
/// Gets the tile grid to be used by TileLayers.
/// </summary>
public Int32Rect TileGrid { get; private set; }
/// <summary>
/// Gets the map scale at the specified location as viewport coordinate units (pixels) per meter.
/// </summary>
@ -258,7 +280,7 @@ namespace MapControl
/// </summary>
public Point LocationToViewportPoint(Location location)
{
return ViewportTransform.Transform(mapTransform.Transform(location));
return viewportTransform.Transform(mapTransform.Transform(location));
}
/// <summary>
@ -266,7 +288,7 @@ namespace MapControl
/// </summary>
public Location ViewportPointToLocation(Point point)
{
return mapTransform.Transform(ViewportTransform.Inverse.Transform(point));
return mapTransform.Transform(viewportTransform.Inverse.Transform(point));
}
/// <summary>
@ -365,105 +387,27 @@ namespace MapControl
{
if (southWest.Latitude < northEast.Latitude && southWest.Longitude < northEast.Longitude)
{
var p1 = MapTransform.Transform(southWest);
var p2 = MapTransform.Transform(northEast);
var p1 = mapTransform.Transform(southWest);
var p2 = mapTransform.Transform(northEast);
var lonScale = RenderSize.Width / (p2.X - p1.X) * 360d / TileSource.TileSize;
var latScale = RenderSize.Height / (p2.Y - p1.Y) * 360d / TileSource.TileSize;
var lonZoom = Math.Log(lonScale, 2d);
var latZoom = Math.Log(latScale, 2d);
TargetZoomLevel = Math.Min(lonZoom, latZoom);
TargetCenter = MapTransform.Transform(new Point((p1.X + p2.X) / 2d, (p1.Y + p2.Y) / 2d));
TargetCenter = mapTransform.Transform(new Point((p1.X + p2.X) / 2d, (p1.Y + p2.Y) / 2d));
TargetHeading = 0d;
}
}
protected override void OnViewportChanged()
{
base.OnViewportChanged();
if (ViewportChanged != null)
{
ViewportChanged(this, EventArgs.Empty);
}
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Loaded -= OnLoaded;
if (TileLayer == null)
if (tileLayerPanel.Children.Count == 0 && !Children.OfType<TileLayer>().Any())
{
TileLayer = TileLayer.Default;
}
UpdateTransform();
}
private void TileLayerCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
tileContainer.AddTileLayers(e.NewStartingIndex, e.NewItems.Cast<TileLayer>());
break;
case NotifyCollectionChangedAction.Remove:
tileContainer.RemoveTileLayers(e.OldStartingIndex, e.OldItems.Count);
break;
#if !SILVERLIGHT
case NotifyCollectionChangedAction.Move:
#endif
case NotifyCollectionChangedAction.Replace:
tileContainer.RemoveTileLayers(e.NewStartingIndex, e.OldItems.Count);
tileContainer.AddTileLayers(e.NewStartingIndex, e.NewItems.Cast<TileLayer>());
break;
case NotifyCollectionChangedAction.Reset:
tileContainer.ClearTileLayers();
if (e.NewItems != null)
{
tileContainer.AddTileLayers(0, e.NewItems.Cast<TileLayer>());
}
break;
default:
break;
}
var firstTileLayer = TileLayers.FirstOrDefault();
if (TileLayer != firstTileLayer)
{
TileLayer = firstTileLayer;
}
}
private void TileLayersPropertyChanged(TileLayerCollection oldTileLayers, TileLayerCollection newTileLayers)
{
tileContainer.ClearTileLayers();
if (oldTileLayers != null)
{
oldTileLayers.CollectionChanged -= TileLayerCollectionChanged;
}
if (newTileLayers != null)
{
newTileLayers.CollectionChanged += TileLayerCollectionChanged;
tileContainer.AddTileLayers(0, newTileLayers);
var firstTileLayer = TileLayers.FirstOrDefault();
if (TileLayer != firstTileLayer)
{
TileLayer = firstTileLayer;
}
}
else
{
TileLayer = null;
}
}
private void TileLayerPropertyChanged(TileLayer tileLayer)
@ -484,35 +428,94 @@ namespace MapControl
TileLayers[0] = tileLayer;
}
}
if (tileLayer != null && tileLayer.Background != null)
{
if (storedBackground == null)
{
storedBackground = Background;
}
private void TileLayersPropertyChanged(TileLayerCollection oldTileLayers, TileLayerCollection newTileLayers)
{
if (oldTileLayers != null)
{
oldTileLayers.CollectionChanged -= TileLayerCollectionChanged;
RemoveTileLayers(0, oldTileLayers.Count);
}
if (newTileLayers != null)
{
AddTileLayers(0, newTileLayers);
newTileLayers.CollectionChanged += TileLayerCollectionChanged;
}
TileLayer = TileLayers.FirstOrDefault();
}
private void TileLayerCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
AddTileLayers(e.NewStartingIndex, e.NewItems.Cast<TileLayer>());
break;
case NotifyCollectionChangedAction.Remove:
RemoveTileLayers(e.OldStartingIndex, e.OldItems.Count);
break;
#if !SILVERLIGHT
case NotifyCollectionChangedAction.Move:
#endif
case NotifyCollectionChangedAction.Replace:
RemoveTileLayers(e.NewStartingIndex, e.OldItems.Count);
AddTileLayers(e.NewStartingIndex, e.NewItems.Cast<TileLayer>());
break;
case NotifyCollectionChangedAction.Reset:
if (e.OldItems != null)
{
RemoveTileLayers(0, e.OldItems.Count);
}
if (e.NewItems != null)
{
AddTileLayers(0, e.NewItems.Cast<TileLayer>());
}
break;
default:
break;
}
TileLayer = TileLayers.FirstOrDefault();
}
private void AddTileLayers(int index, IEnumerable<TileLayer> tileLayers)
{
foreach (var tileLayer in tileLayers)
{
if (index == 0)
{
if (tileLayer.Background != null)
{
Background = tileLayer.Background;
}
else if (storedBackground != null)
{
Background = storedBackground;
storedBackground = null;
}
if (tileLayer != null && tileLayer.Foreground != null)
if (tileLayer.Foreground != null)
{
if (storedForeground == null)
{
storedForeground = Foreground;
}
Foreground = tileLayer.Foreground;
}
else if (storedForeground != null)
}
tileLayerPanel.Children.Insert(index++, tileLayer);
}
}
private void RemoveTileLayers(int index, int count)
{
Foreground = storedForeground;
storedForeground = null;
while (count-- > 0)
{
tileLayerPanel.Children.RemoveAt(index + count);
}
if (index == 0)
{
ClearValue(BackgroundProperty);
ClearValue(ForegroundProperty);
}
}
@ -552,7 +555,7 @@ namespace MapControl
if (centerAnimation == null)
{
InternalSetValue(TargetCenterProperty, center);
InternalSetValue(CenterPointProperty, MapTransform.Transform(center));
InternalSetValue(CenterPointProperty, mapTransform.Transform(center));
}
}
}
@ -573,10 +576,10 @@ namespace MapControl
// animate private CenterPoint property by PointAnimation
centerAnimation = new PointAnimation
{
From = MapTransform.Transform(Center),
To = MapTransform.Transform(targetCenter, Center.Longitude),
Duration = Settings.MapAnimationDuration,
EasingFunction = Settings.MapAnimationEasingFunction,
From = mapTransform.Transform(Center),
To = mapTransform.Transform(targetCenter, Center.Longitude),
Duration = AnimationDuration,
EasingFunction = AnimationEasingFunction,
FillBehavior = FillBehavior.HoldEnd
};
@ -594,7 +597,7 @@ namespace MapControl
centerAnimation = null;
InternalSetValue(CenterProperty, TargetCenter);
InternalSetValue(CenterPointProperty, MapTransform.Transform(TargetCenter));
InternalSetValue(CenterPointProperty, mapTransform.Transform(TargetCenter));
RemoveAnimation(CenterPointProperty); // remove holding animation in WPF
ResetTransformOrigin();
@ -607,7 +610,7 @@ namespace MapControl
if (!internalPropertyChange)
{
centerPoint.X = Location.NormalizeLongitude(centerPoint.X);
InternalSetValue(CenterProperty, MapTransform.Transform(centerPoint));
InternalSetValue(CenterProperty, mapTransform.Transform(centerPoint));
ResetTransformOrigin();
UpdateTransform();
}
@ -680,8 +683,8 @@ namespace MapControl
zoomLevelAnimation = new DoubleAnimation
{
To = targetZoomLevel,
Duration = Settings.MapAnimationDuration,
EasingFunction = Settings.MapAnimationEasingFunction,
Duration = AnimationDuration,
EasingFunction = AnimationEasingFunction,
FillBehavior = FillBehavior.HoldEnd
};
@ -755,8 +758,8 @@ namespace MapControl
headingAnimation = new DoubleAnimation
{
By = delta,
Duration = Settings.MapAnimationDuration,
EasingFunction = Settings.MapAnimationEasingFunction,
Duration = AnimationDuration,
EasingFunction = AnimationEasingFunction,
FillBehavior = FillBehavior.HoldEnd
};
@ -784,7 +787,12 @@ namespace MapControl
{
Location center;
if (transformOrigin != null)
if (transformOrigin == null)
{
center = Center;
SetViewportTransform(center);
}
else
{
SetViewportTransform(transformOrigin);
@ -802,7 +810,7 @@ namespace MapControl
if (centerAnimation == null)
{
InternalSetValue(TargetCenterProperty, center);
InternalSetValue(CenterPointProperty, MapTransform.Transform(center));
InternalSetValue(CenterPointProperty, mapTransform.Transform(center));
}
if (resetTransformOrigin)
@ -811,11 +819,6 @@ namespace MapControl
SetViewportTransform(center);
}
}
else
{
center = Center;
SetViewportTransform(center);
}
CenterScale = ViewportScale * mapTransform.RelativeScale(center) / TileSource.MetersPerDegree; // Pixels per meter at center latitude
@ -823,9 +826,77 @@ namespace MapControl
OnViewportChanged();
}
protected override void OnViewportChanged()
{
base.OnViewportChanged();
var viewportChanged = ViewportChanged;
if (viewportChanged != null)
{
viewportChanged(this, EventArgs.Empty);
}
}
private void SetViewportTransform(Location origin)
{
ViewportScale = tileContainer.SetViewportTransform(ZoomLevel, Heading, mapTransform.Transform(origin), viewportOrigin);
var oldMapOriginX = (viewportOrigin.X - tileLayerOffset.X) / ViewportScale - 180d;
var mapOrigin = mapTransform.Transform(origin);
ViewportScale = Math.Pow(2d, ZoomLevel) * TileSource.TileSize / 360d;
SetViewportTransform(mapOrigin);
tileLayerOffset.X = viewportOrigin.X - (180d + mapOrigin.X) * ViewportScale;
tileLayerOffset.Y = viewportOrigin.Y - (180d - mapOrigin.Y) * ViewportScale;
if (Math.Abs(mapOrigin.X - oldMapOriginX) > 180d)
{
// immediately handle map origin leap when map center moves across 180° longitude
UpdateTiles(this, EventArgs.Empty);
}
else
{
SetTileLayerTransform();
tileUpdateTimer.Start();
}
}
private void UpdateTiles(object sender, object e)
{
tileUpdateTimer.Stop();
// relative size of scaled tile ranges from 0.75 to 1.5 (192 to 384 pixels)
var zoomLevelSwitchDelta = Math.Log(0.75, 2d);
var zoomLevel = (int)Math.Floor(ZoomLevel - zoomLevelSwitchDelta);
var transform = GetTileIndexMatrix((double)(1 << zoomLevel) / 360d);
// tile indices of visible rectangle
var p1 = transform.Transform(new Point(0d, 0d));
var p2 = transform.Transform(new Point(RenderSize.Width, 0d));
var p3 = transform.Transform(new Point(0d, RenderSize.Height));
var p4 = transform.Transform(new Point(RenderSize.Width, RenderSize.Height));
// index ranges of visible tiles
var x1 = (int)Math.Floor(Math.Min(p1.X, Math.Min(p2.X, Math.Min(p3.X, p4.X))));
var y1 = (int)Math.Floor(Math.Min(p1.Y, Math.Min(p2.Y, Math.Min(p3.Y, p4.Y))));
var x2 = (int)Math.Floor(Math.Max(p1.X, Math.Max(p2.X, Math.Max(p3.X, p4.X))));
var y2 = (int)Math.Floor(Math.Max(p1.Y, Math.Max(p2.Y, Math.Max(p3.Y, p4.Y))));
var grid = new Int32Rect(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
if (TileZoomLevel != zoomLevel || TileGrid != grid)
{
TileZoomLevel = zoomLevel;
TileGrid = grid;
SetTileLayerTransform();
var tileGridChanged = TileGridChanged;
if (tileGridChanged != null)
{
tileGridChanged(this, EventArgs.Empty);
}
}
}
}
}

View file

@ -23,7 +23,7 @@
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>Bin\Debug</OutputPath>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE</DefineConstants>
<NoStdLib>true</NoStdLib>
<NoConfig>true</NoConfig>
@ -33,7 +33,7 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>Bin\Release</OutputPath>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE;SILVERLIGHT;WINDOWS_PHONE</DefineConstants>
<NoStdLib>true</NoStdLib>
<NoConfig>true</NoConfig>
@ -48,6 +48,7 @@
<Compile Include="ImageTileSource.Silverlight.WinRT.cs" />
<Compile Include="IMapElement.cs" />
<Compile Include="Int32Rect.cs" />
<Compile Include="ITileImageLoader.cs" />
<Compile Include="Location.cs" />
<Compile Include="LocationCollection.cs" />
<Compile Include="LocationCollectionConverter.cs" />
@ -78,13 +79,11 @@
<Compile Include="PanelBase.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Pushpin.Silverlight.WinRT.cs" />
<Compile Include="Settings.cs" />
<Compile Include="Tile.cs" />
<Compile Include="Tile.Silverlight.WinRT.cs" />
<Compile Include="TileContainer.cs" />
<Compile Include="TileContainer.Silverlight.WinRT.cs" />
<Compile Include="TileImageLoader.Silverlight.cs" />
<Compile Include="TileLayer.cs" />
<Compile Include="TileLayer.Silverlight.WinRT.cs" />
<Compile Include="TileLayerCollection.cs" />
<Compile Include="TileSource.cs" />
<Compile Include="TileSourceConverter.cs" />

View file

@ -75,6 +75,7 @@
<Compile Include="ImageTileSource.Silverlight.WinRT.cs" />
<Compile Include="IMapElement.cs" />
<Compile Include="Int32Rect.cs" />
<Compile Include="ITileImageLoader.cs" />
<Compile Include="Location.cs" />
<Compile Include="LocationCollection.cs" />
<Compile Include="LocationCollectionConverter.cs" />
@ -105,13 +106,11 @@
<Compile Include="PanelBase.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Pushpin.Silverlight.WinRT.cs" />
<Compile Include="Settings.cs" />
<Compile Include="Tile.cs" />
<Compile Include="Tile.Silverlight.WinRT.cs" />
<Compile Include="TileContainer.cs" />
<Compile Include="TileContainer.Silverlight.WinRT.cs" />
<Compile Include="TileImageLoader.Silverlight.cs" />
<Compile Include="TileLayer.cs" />
<Compile Include="TileLayer.Silverlight.WinRT.cs" />
<Compile Include="TileLayerCollection.cs" />
<Compile Include="TileSource.cs" />
<Compile Include="TileSourceConverter.cs" />

View file

@ -58,6 +58,7 @@
<Compile Include="HyperlinkText.cs" />
<Compile Include="ImageTileSource.WPF.cs" />
<Compile Include="IMapElement.cs" />
<Compile Include="ITileImageLoader.cs" />
<Compile Include="Location.cs" />
<Compile Include="LocationCollection.cs" />
<Compile Include="LocationCollectionConverter.cs" />
@ -77,7 +78,6 @@
<Compile Include="MapPanel.cs" />
<Compile Include="MapPanel.WPF.cs" />
<Compile Include="MapRectangle.WPF.cs" />
<Compile Include="PanelBase.cs" />
<Compile Include="MapPath.cs" />
<Compile Include="MapPath.WPF.cs" />
<Compile Include="MapPolyline.cs" />
@ -86,15 +86,14 @@
<Compile Include="MapScale.cs" />
<Compile Include="MapTransform.cs" />
<Compile Include="MercatorTransform.cs" />
<Compile Include="PanelBase.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Pushpin.WPF.cs" />
<Compile Include="Settings.cs" />
<Compile Include="Tile.cs" />
<Compile Include="Tile.WPF.cs" />
<Compile Include="TileContainer.cs" />
<Compile Include="TileContainer.WPF.cs" />
<Compile Include="TileImageLoader.WPF.cs" />
<Compile Include="TileLayer.cs" />
<Compile Include="TileLayer.WPF.cs" />
<Compile Include="TileLayerCollection.cs" />
<Compile Include="TileSource.cs" />
<Compile Include="TileSourceConverter.cs" />

View file

@ -19,7 +19,7 @@ namespace MapControl
{
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(
"Source", typeof(ImageSource), typeof(MapImage),
new PropertyMetadata(null, (o, e) => ((MapImage)o).SourceChanged((ImageSource)e.NewValue)));
new PropertyMetadata(null, (o, e) => ((MapImage)o).SourcePropertyChanged((ImageSource)e.NewValue)));
public ImageSource Source
{
@ -27,7 +27,7 @@ namespace MapControl
set { SetValue(SourceProperty, value); }
}
private void SourceChanged(ImageSource image)
private void SourcePropertyChanged(ImageSource image)
{
Fill = new ImageBrush
{

View file

@ -40,7 +40,7 @@ namespace MapControl
Children.Add(new MapImage { Opacity = 0d });
Children.Add(new MapImage { Opacity = 0d });
updateTimer = new DispatcherTimer { Interval = Settings.TileUpdateInterval };
updateTimer = new DispatcherTimer { Interval = MapBase.TileUpdateInterval };
updateTimer.Tick += (s, e) => UpdateImage();
}
@ -175,16 +175,16 @@ namespace MapControl
if (topImage.Source != null)
{
topImage.BeginAnimation(UIElement.OpacityProperty,
new DoubleAnimation { To = 1d, Duration = Settings.TileAnimationDuration });
new DoubleAnimation { To = 1d, Duration = Tile.OpacityAnimationDuration });
}
if (bottomImage.Source != null)
{
var fadeOutAnimation = new DoubleAnimation { To = 0d, Duration = Settings.TileAnimationDuration };
var fadeOutAnimation = new DoubleAnimation { To = 0d, Duration = Tile.OpacityAnimationDuration };
if (topImage.Source != null)
{
fadeOutAnimation.BeginTime = Settings.TileAnimationDuration;
fadeOutAnimation.BeginTime = Tile.OpacityAnimationDuration;
}
bottomImage.BeginAnimation(UIElement.OpacityProperty, fadeOutAnimation);

View file

@ -3,12 +3,9 @@
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace MapControl
{
@ -17,10 +14,6 @@ namespace MapControl
/// </summary>
public class MapItemsControl : ListBox
{
public static readonly DependencyProperty SelectionGeometryProperty = DependencyProperty.Register(
"SelectionGeometry", typeof(Geometry), typeof(MapItemsControl),
new PropertyMetadata((o, e) => ((MapItemsControl)o).SelectionGeometryPropertyChanged((Geometry)e.NewValue)));
static MapItemsControl()
{
DefaultStyleKeyProperty.OverrideMetadata(
@ -43,58 +36,6 @@ namespace MapControl
return item is MapItem;
}
/// <summary>
/// Gets or sets a Geometry that selects all items inside its fill area, i.e.
/// where Geometry.FillContains returns true for the item's viewport position.
/// </summary>
public Geometry SelectionGeometry
{
get { return (Geometry)GetValue(SelectionGeometryProperty); }
set { SetValue(SelectionGeometryProperty, value); }
}
public object GetFirstItemInGeometry(Geometry geometry)
{
if (geometry == null || geometry.IsEmpty())
{
return null;
}
return Items.Cast<object>().FirstOrDefault(i => IsItemInGeometry(i, geometry));
}
public IList<object> GetItemsInGeometry(Geometry geometry)
{
if (geometry == null || geometry.IsEmpty())
{
return null;
}
return Items.Cast<object>().Where(i => IsItemInGeometry(i, geometry)).ToList();
}
private bool IsItemInGeometry(object item, Geometry geometry)
{
var container = ItemContainerGenerator.ContainerFromItem(item) as UIElement;
Point? viewportPosition;
return container != null &&
(viewportPosition = MapPanel.GetViewportPosition(container)).HasValue &&
geometry.FillContains(viewportPosition.Value);
}
private void SelectionGeometryPropertyChanged(Geometry geometry)
{
if (SelectionMode == SelectionMode.Single)
{
SelectedItem = GetFirstItemInGeometry(geometry);
}
else
{
SetSelectedItems(GetItemsInGeometry(geometry));
}
}
private void CurrentItemChanging(object sender, CurrentChangingEventArgs e)
{
var container = ItemContainerGenerator.ContainerFromItem(Items.CurrentItem) as UIElement;

View file

@ -4,11 +4,9 @@
#if WINDOWS_RUNTIME
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
#else
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
#endif
@ -21,7 +19,11 @@ namespace MapControl
public MapPanel()
{
if (!(this is MapBase))
if (this is MapBase)
{
SetValue(ParentMapProperty, this);
}
else
{
AddParentMapHandlers(this);
}
@ -67,10 +69,5 @@ namespace MapControl
return parentMap;
}
internal void SetParentMap()
{
SetValue(ParentMapProperty, this);
}
}
}

View file

@ -14,14 +14,17 @@ namespace MapControl
public static readonly DependencyProperty ParentMapProperty = ParentMapPropertyKey.DependencyProperty;
public static MapBase GetParentMap(UIElement element)
public MapPanel()
{
return (MapBase)element.GetValue(ParentMapProperty);
}
internal void SetParentMap()
if (this is MapBase)
{
SetValue(ParentMapPropertyKey, this);
}
}
public static MapBase GetParentMap(UIElement element)
{
return (MapBase)element.GetValue(ParentMapProperty);
}
}
}

View file

@ -24,9 +24,6 @@ namespace MapControl
public static readonly DependencyProperty LocationProperty = DependencyProperty.RegisterAttached(
"Location", typeof(Location), typeof(MapPanel), new PropertyMetadata(null, LocationPropertyChanged));
public static readonly DependencyProperty ViewportPositionProperty = DependencyProperty.RegisterAttached(
"ViewportPosition", typeof(Point?), typeof(MapPanel), null);
public static Location GetLocation(UIElement element)
{
return (Location)element.GetValue(LocationProperty);
@ -37,21 +34,12 @@ namespace MapControl
element.SetValue(LocationProperty, value);
}
public static Point? GetViewportPosition(UIElement element)
{
return (Point?)element.GetValue(ViewportPositionProperty);
}
private MapBase parentMap;
public MapBase ParentMap
{
get { return parentMap; }
}
void IMapElement.SetParentMap(MapBase map)
{
SetParentMapOverride(map);
set { SetParentMapOverride(value); }
}
protected virtual void SetParentMapOverride(MapBase map)
@ -114,7 +102,7 @@ namespace MapControl
if (mapElement != null)
{
mapElement.SetParentMap(e.NewValue as MapBase);
mapElement.ParentMap = e.NewValue as MapBase;
}
}
@ -149,12 +137,10 @@ namespace MapControl
{
var mapPosition = parentMap.MapTransform.Transform(location, parentMap.Center.Longitude); // nearest to center longitude
viewportPosition = parentMap.ViewportTransform.Transform(mapPosition);
element.SetValue(ViewportPositionProperty, viewportPosition);
}
else
{
viewportPosition = new Point();
element.ClearValue(ViewportPositionProperty);
}
var translateTransform = element.RenderTransform as TranslateTransform;
@ -189,7 +175,7 @@ namespace MapControl
private static void ArrangeElementWithLocation(UIElement element)
{
var rect = new Rect(0d, 0d, element.DesiredSize.Width, element.DesiredSize.Height);
var rect = new Rect(new Point(), element.DesiredSize);
var frameworkElement = element as FrameworkElement;
if (frameworkElement != null)
@ -228,7 +214,7 @@ namespace MapControl
private static void ArrangeElementWithoutLocation(UIElement element, Size parentSize)
{
var rect = new Rect(0d, 0d, element.DesiredSize.Width, element.DesiredSize.Height);
var rect = new Rect(new Point(), element.DesiredSize);
var frameworkElement = element as FrameworkElement;
if (frameworkElement != null)

View file

@ -20,13 +20,12 @@ namespace MapControl
public MapBase ParentMap
{
get { return parentMap; }
}
void IMapElement.SetParentMap(MapBase map)
set
{
parentMap = map;
parentMap = value;
UpdateData();
}
}
protected virtual void UpdateData()
{

View file

@ -7,7 +7,6 @@ using System.Linq;
using Windows.UI.Xaml.Media;
#else
using System.Windows.Media;
#endif
namespace MapControl

View file

@ -7,7 +7,6 @@ using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
#else
using System;
using System.Windows;
using System.Windows.Media;
#endif
@ -82,10 +81,9 @@ namespace MapControl
!double.IsNaN(West) && !double.IsNaN(East) &&
South < North && West < East)
{
var p1 = ParentMap.MapTransform.Transform(new Location(South, West));
var p2 = ParentMap.MapTransform.Transform(new Location(North, East));
SetRect(new Rect(p1.X, p1.Y, p2.X - p1.X, p2.Y - p1.Y));
SetRect(new Rect(
ParentMap.MapTransform.Transform(new Location(South, West)),
ParentMap.MapTransform.Transform(new Location(North, East))));
}
else
{

View file

@ -14,7 +14,7 @@ using System.Windows.Controls;
namespace MapControl
{
/// <summary>
/// Common base class for MapPanel, TileLayer and TileContainer.
/// Common base class for MapPanel and TileLayer.
/// </summary>
public class PanelBase : Panel
{
@ -32,9 +32,9 @@ namespace MapControl
protected override Size ArrangeOverride(Size finalSize)
{
foreach (UIElement child in Children)
foreach (UIElement element in Children)
{
child.Arrange(new Rect(new Point(), finalSize));
element.Arrange(new Rect(new Point(), finalSize));
}
return finalSize;

View file

@ -17,8 +17,8 @@ using System.Windows;
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.3.1")]
[assembly: AssemblyFileVersion("2.3.1")]
[assembly: AssemblyVersion("2.4.0")]
[assembly: AssemblyFileVersion("2.4.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]

View file

@ -1,32 +0,0 @@
// XAML Map Control - http://xamlmapcontrol.codeplex.com/
// Copyright © 2014 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
#if WINDOWS_RUNTIME
using Windows.UI.Xaml.Media.Animation;
#else
using System.Windows.Media.Animation;
#endif
namespace MapControl
{
/// <summary>
/// Stores global static properties that control the behaviour of the map control.
/// </summary>
public static class Settings
{
public static TimeSpan TileUpdateInterval { get; set; }
public static TimeSpan TileAnimationDuration { get; set; }
public static TimeSpan MapAnimationDuration { get; set; }
public static EasingFunctionBase MapAnimationEasingFunction { get; set; }
static Settings()
{
TileUpdateInterval = TimeSpan.FromSeconds(0.5);
TileAnimationDuration = TimeSpan.FromSeconds(0.3);
MapAnimationDuration = TimeSpan.FromSeconds(0.3);
MapAnimationEasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut };
}
}
}

View file

@ -2,6 +2,7 @@
// Copyright © 2014 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
#if WINDOWS_RUNTIME
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
@ -20,15 +21,15 @@ namespace MapControl
{
public partial class Tile
{
public void SetImageSource(ImageSource image, bool animateOpacity)
public void SetImage(ImageSource image, bool animateOpacity = true, bool isDownloading = true)
{
if (image != null && Image.Source == null)
{
if (animateOpacity)
if (animateOpacity && OpacityAnimationDuration > TimeSpan.Zero)
{
var bitmap = image as BitmapImage;
BitmapImage bitmap;
if (bitmap != null)
if (isDownloading && (bitmap = image as BitmapImage) != null)
{
bitmap.ImageOpened += BitmapImageOpened;
bitmap.ImageFailed += BitmapImageFailed;
@ -36,17 +37,18 @@ namespace MapControl
else
{
Image.BeginAnimation(Image.OpacityProperty,
new DoubleAnimation { To = 1d, Duration = Settings.TileAnimationDuration });
new DoubleAnimation { From = 0d, To = 1d, Duration = OpacityAnimationDuration });
}
}
else
{
Image.Opacity = 1d;
}
}
Image.Source = image;
HasImageSource = true;
}
Pending = false;
}
private void BitmapImageOpened(object sender, RoutedEventArgs e)
@ -57,7 +59,7 @@ namespace MapControl
bitmap.ImageFailed -= BitmapImageFailed;
Image.BeginAnimation(Image.OpacityProperty,
new DoubleAnimation { To = 1d, Duration = Settings.TileAnimationDuration });
new DoubleAnimation { From = 0d, To = 1d, Duration = OpacityAnimationDuration });
}
private void BitmapImageFailed(object sender, ExceptionRoutedEventArgs e)

View file

@ -12,11 +12,11 @@ namespace MapControl
{
public partial class Tile
{
public void SetImageSource(ImageSource image, bool animateOpacity)
public void SetImage(ImageSource image, bool animateOpacity = true)
{
if (image != null && Image.Source == null)
{
if (animateOpacity)
if (animateOpacity && OpacityAnimationDuration > TimeSpan.Zero)
{
var bitmap = image as BitmapSource;
@ -28,17 +28,18 @@ namespace MapControl
else
{
Image.BeginAnimation(Image.OpacityProperty,
new DoubleAnimation(1d, Settings.TileAnimationDuration));
new DoubleAnimation(0d, 1d, OpacityAnimationDuration));
}
}
else
{
Image.Opacity = 1d;
}
}
Image.Source = image;
HasImageSource = true;
}
Pending = false;
}
private void BitmapDownloadCompleted(object sender, EventArgs e)
@ -49,7 +50,7 @@ namespace MapControl
bitmap.DownloadFailed -= BitmapDownloadFailed;
Image.BeginAnimation(Image.OpacityProperty,
new DoubleAnimation(1d, Settings.TileAnimationDuration));
new DoubleAnimation(0d, 1d, OpacityAnimationDuration));
}
private void BitmapDownloadFailed(object sender, ExceptionEventArgs e)

View file

@ -13,6 +13,8 @@ namespace MapControl
{
public partial class Tile
{
public static TimeSpan OpacityAnimationDuration = TimeSpan.FromSeconds(0.3);
public readonly int ZoomLevel;
public readonly int X;
public readonly int Y;
@ -23,9 +25,10 @@ namespace MapControl
ZoomLevel = zoomLevel;
X = x;
Y = y;
Pending = true;
}
public bool HasImageSource { get; private set; }
public bool Pending { get; private set; }
public int XIndex
{

View file

@ -1,49 +0,0 @@
// XAML Map Control - http://xamlmapcontrol.codeplex.com/
// Copyright © 2014 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
#if WINDOWS_RUNTIME
using Windows.Foundation;
using Windows.UI.Xaml.Media;
#else
using System.Windows;
using System.Windows.Media;
#endif
namespace MapControl
{
internal partial class TileContainer
{
private Matrix GetTileIndexMatrix(double scale)
{
return ViewportTransform.Matrix
.Invert() // view to map coordinates
.Translate(180d, -180d)
.Scale(scale, -scale); // map coordinates to tile indices
}
private void UpdateViewportTransform(double scale, Point mapOrigin)
{
ViewportTransform.Matrix = Matrix.Identity
.Translate(-mapOrigin.X, -mapOrigin.Y)
.Scale(scale, -scale)
.Rotate(rotation)
.Translate(viewportOrigin.X, viewportOrigin.Y);
}
/// <summary>
/// Sets a RenderTransform with origin at tileGrid.X and tileGrid.Y to minimize rounding errors.
/// </summary>
private void UpdateRenderTransform()
{
var scale = Math.Pow(2d, zoomLevel - tileZoomLevel);
((MatrixTransform)RenderTransform).Matrix = Matrix.Identity
.Translate(tileGrid.X * TileSource.TileSize, tileGrid.Y * TileSource.TileSize)
.Scale(scale, scale)
.Translate(tileLayerOffset.X, tileLayerOffset.Y)
.RotateAt(rotation, viewportOrigin.X, viewportOrigin.Y);
}
}
}

View file

@ -1,49 +0,0 @@
// XAML Map Control - http://xamlmapcontrol.codeplex.com/
// Copyright © 2014 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Windows;
using System.Windows.Media;
namespace MapControl
{
internal partial class TileContainer
{
private Matrix GetTileIndexMatrix(double scale)
{
var transform = ViewportTransform.Matrix;
transform.Invert(); // view to map coordinates
transform.Translate(180d, -180d);
transform.Scale(scale, -scale); // map coordinates to tile indices
return transform;
}
private void UpdateViewportTransform(double scale, Point mapOrigin)
{
var transform = Matrix.Identity;
transform.Translate(-mapOrigin.X, -mapOrigin.Y);
transform.Scale(scale, -scale);
transform.Rotate(rotation);
transform.Translate(viewportOrigin.X, viewportOrigin.Y);
ViewportTransform.Matrix = transform;
}
/// <summary>
/// Sets a RenderTransform with origin at tileGrid.X and tileGrid.Y to minimize rounding errors.
/// </summary>
private void UpdateRenderTransform()
{
var scale = Math.Pow(2d, zoomLevel - tileZoomLevel);
var transform = Matrix.Identity;
transform.Translate(tileGrid.X * TileSource.TileSize, tileGrid.Y * TileSource.TileSize);
transform.Scale(scale, scale);
transform.Translate(tileLayerOffset.X, tileLayerOffset.Y);
transform.RotateAt(rotation, viewportOrigin.X, viewportOrigin.Y);
((MatrixTransform)RenderTransform).Matrix = transform;
}
}
}

View file

@ -1,151 +0,0 @@
// XAML Map Control - http://xamlmapcontrol.codeplex.com/
// Copyright © 2014 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Collections.Generic;
using System.Linq;
#if WINDOWS_RUNTIME
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
#else
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
#endif
namespace MapControl
{
internal partial class TileContainer : PanelBase
{
// relative size of scaled tile ranges from 0.75 to 1.5 (192 to 384 pixels)
private static double zoomLevelSwitchDelta = -Math.Log(0.75, 2d);
private readonly DispatcherTimer updateTimer;
private Point viewportOrigin;
private Point tileLayerOffset;
private double rotation;
private double zoomLevel;
private int tileZoomLevel;
private Int32Rect tileGrid;
public readonly MatrixTransform ViewportTransform = new MatrixTransform();
public TileContainer()
{
RenderTransform = new MatrixTransform();
updateTimer = new DispatcherTimer { Interval = Settings.TileUpdateInterval };
updateTimer.Tick += UpdateTiles;
}
public IEnumerable<TileLayer> TileLayers
{
get { return Children.Cast<TileLayer>(); }
}
public void AddTileLayers(int index, IEnumerable<TileLayer> tileLayers)
{
foreach (var tileLayer in tileLayers)
{
if (index < Children.Count)
{
Children.Insert(index, tileLayer);
}
else
{
Children.Add(tileLayer);
}
index++;
tileLayer.UpdateTiles(tileZoomLevel, tileGrid);
}
}
public void RemoveTileLayers(int index, int count)
{
while (count-- > 0)
{
((TileLayer)Children[index]).ClearTiles();
Children.RemoveAt(index);
}
}
public void ClearTileLayers()
{
foreach (TileLayer tileLayer in Children)
{
tileLayer.ClearTiles();
}
Children.Clear();
}
public double SetViewportTransform(double mapZoomLevel, double mapRotation, Point mapOrigin, Point viewOrigin)
{
var scale = Math.Pow(2d, zoomLevel) * TileSource.TileSize / 360d;
var oldMapOriginX = (viewportOrigin.X - tileLayerOffset.X) / scale - 180d;
if (zoomLevel != mapZoomLevel)
{
zoomLevel = mapZoomLevel;
scale = Math.Pow(2d, zoomLevel) * TileSource.TileSize / 360d;
}
rotation = mapRotation;
viewportOrigin = viewOrigin;
tileLayerOffset.X = viewportOrigin.X - (180d + mapOrigin.X) * scale;
tileLayerOffset.Y = viewportOrigin.Y - (180d - mapOrigin.Y) * scale;
UpdateViewportTransform(scale, mapOrigin);
UpdateRenderTransform();
if (Math.Abs(mapOrigin.X - oldMapOriginX) > 180d)
{
// immediately handle map origin leap when map center moves across 180° longitude
UpdateTiles(this, EventArgs.Empty);
}
else
{
updateTimer.Start();
}
return scale;
}
private void UpdateTiles(object sender, object e)
{
updateTimer.Stop();
var zoom = (int)Math.Floor(zoomLevel + zoomLevelSwitchDelta);
var scale = (double)(1 << zoom) / 360d;
var transform = GetTileIndexMatrix(scale);
// tile indices of visible rectangle
var p1 = transform.Transform(new Point(0d, 0d));
var p2 = transform.Transform(new Point(RenderSize.Width, 0d));
var p3 = transform.Transform(new Point(0d, RenderSize.Height));
var p4 = transform.Transform(new Point(RenderSize.Width, RenderSize.Height));
// index ranges of visible tiles
var x1 = (int)Math.Floor(Math.Min(p1.X, Math.Min(p2.X, Math.Min(p3.X, p4.X))));
var y1 = (int)Math.Floor(Math.Min(p1.Y, Math.Min(p2.Y, Math.Min(p3.Y, p4.Y))));
var x2 = (int)Math.Floor(Math.Max(p1.X, Math.Max(p2.X, Math.Max(p3.X, p4.X))));
var y2 = (int)Math.Floor(Math.Max(p1.Y, Math.Max(p2.Y, Math.Max(p3.Y, p4.Y))));
var grid = new Int32Rect(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
if (tileZoomLevel != zoom || tileGrid != grid)
{
tileZoomLevel = zoom;
tileGrid = grid;
UpdateRenderTransform();
foreach (TileLayer tileLayer in Children)
{
tileLayer.UpdateTiles(tileZoomLevel, tileGrid);
}
}
}
}
}

View file

@ -39,7 +39,7 @@ namespace MapControl
}
}
tile.SetImageSource(image, tileLayer.AnimateTileOpacity);
tile.SetImage(image);
}
catch (Exception ex)
{

View file

@ -12,6 +12,7 @@ using System.Linq;
using System.Net;
using System.Runtime.Caching;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
@ -24,53 +25,62 @@ namespace MapControl
public class TileImageLoader : ITileImageLoader
{
/// <summary>
/// Default Name of an ObjectCache instance that is assigned to the Cache property.
/// Default name of an ObjectCache instance that is assigned to the Cache property.
/// </summary>
public const string DefaultCacheName = "TileCache";
/// <summary>
/// Default value for the directory where an ObjectCache instance may save cached data.
/// Default folder path where an ObjectCache instance may save cached data.
/// </summary>
public static readonly string DefaultCacheDirectory =
public static readonly string DefaultCacheFolder =
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "MapControl");
/// <summary>
/// Default expiration time span for cached images. Used when no expiration date
/// was transmitted on download. The default value is seven days.
/// Default expiration time for cached tile images. Used when no expiration time
/// was transmitted on download. The default and recommended minimum value is seven days.
/// See OpenStreetMap tile usage policy: http://wiki.openstreetmap.org/wiki/Tile_usage_policy
/// </summary>
public static TimeSpan DefaultCacheExpiration { get; set; }
public static TimeSpan DefaultCacheExpiration = TimeSpan.FromDays(7);
/// <summary>
/// The ObjectCache used to cache tile images. The default is MemoryCache.Default.
/// </summary>
public static ObjectCache Cache { get; set; }
public static ObjectCache Cache = MemoryCache.Default;
static TileImageLoader()
/// <summary>
/// Optional value to be used for the HttpWebRequest.UserAgent property. The default is null.
/// </summary>
public static string HttpUserAgent;
private class PendingTile
{
DefaultCacheExpiration = TimeSpan.FromDays(7);
Cache = MemoryCache.Default;
public readonly Tile Tile;
public readonly ImageSource CachedImage;
public PendingTile(Tile tile, ImageSource cachedImage = null)
{
Tile = tile;
CachedImage = cachedImage;
}
}
private readonly ConcurrentQueue<Tile> pendingTiles = new ConcurrentQueue<Tile>();
private int threadCount;
private readonly ConcurrentQueue<PendingTile> pendingTiles = new ConcurrentQueue<PendingTile>();
private int taskCount;
public void BeginLoadTiles(TileLayer tileLayer, IEnumerable<Tile> tiles)
{
if (tiles.Any())
{
// get current TileLayer property values in UI thread
var dispatcher = tileLayer.Dispatcher;
var tileSource = tileLayer.TileSource;
var imageTileSource = tileSource as ImageTileSource;
var animateOpacity = tileLayer.AnimateTileOpacity;
var dispatcher = tileLayer.Dispatcher;
if (imageTileSource != null && !imageTileSource.IsAsync) // call LoadImage in UI thread
if (imageTileSource != null && !imageTileSource.IsAsync) // call LoadImage in UI thread with low priority
{
var setImageAction = new Action<Tile>(t => t.SetImageSource(LoadImage(imageTileSource, t), animateOpacity));
foreach (var tile in tiles)
{
dispatcher.BeginInvoke(setImageAction, DispatcherPriority.Background, tile); // with low priority
dispatcher.BeginInvoke(new Action<Tile>(t => t.SetImage(LoadImage(imageTileSource, t))), DispatcherPriority.Background, tile);
}
}
else
@ -79,70 +89,65 @@ namespace MapControl
var sourceName = tileLayer.SourceName;
var maxDownloads = tileLayer.MaxParallelDownloads;
ThreadPool.QueueUserWorkItem(o =>
GetTiles(tileList, dispatcher, tileSource, sourceName, animateOpacity, maxDownloads));
Task.Run(() => GetTiles(tileList, dispatcher, tileSource, sourceName, maxDownloads));
}
}
}
public void CancelLoadTiles(TileLayer tileLayer)
{
Tile tile;
while (pendingTiles.TryDequeue(out tile)) ; // no Clear method
PendingTile pendingTile;
while (pendingTiles.TryDequeue(out pendingTile)) ; // no Clear method
}
private void GetTiles(List<Tile> tiles, Dispatcher dispatcher, TileSource tileSource, string sourceName, bool animateOpacity, int maxDownloads)
private void GetTiles(List<Tile> tiles, Dispatcher dispatcher, TileSource tileSource, string sourceName, int maxDownloads)
{
if (Cache != null && !string.IsNullOrWhiteSpace(sourceName) &&
!(tileSource is ImageTileSource) && !tileSource.UriFormat.StartsWith("file:"))
if (Cache != null &&
!string.IsNullOrWhiteSpace(sourceName) &&
!(tileSource is ImageTileSource) &&
!tileSource.UriFormat.StartsWith("file:"))
{
var setImageAction = new Action<Tile, ImageSource>((t, i) => t.SetImageSource(i, animateOpacity));
var outdatedTiles = new List<Tile>(tiles.Count);
foreach (var tile in tiles)
{
var buffer = Cache.Get(TileCache.Key(sourceName, tile)) as byte[];
var image = CreateImage(buffer);
BitmapSource image;
if (image == null)
if (GetCachedImage(GetCacheKey(sourceName, tile), out image))
{
pendingTiles.Enqueue(tile); // not yet cached
}
else if (TileCache.IsExpired(buffer))
{
dispatcher.Invoke(setImageAction, tile, image); // synchronously before enqueuing
outdatedTiles.Add(tile); // update outdated cache
dispatcher.BeginInvoke(new Action<Tile, ImageSource>((t, i) => t.SetImage(i)), tile, image);
}
else
{
dispatcher.BeginInvoke(setImageAction, tile, image);
pendingTiles.Enqueue(new PendingTile(tile, image));
}
}
tiles = outdatedTiles; // enqueue outdated tiles after current tiles
}
else
{
foreach (var tile in tiles)
{
pendingTiles.Enqueue(tile);
pendingTiles.Enqueue(new PendingTile(tile));
}
}
while (threadCount < Math.Min(pendingTiles.Count, maxDownloads))
var newTaskCount = Math.Min(pendingTiles.Count, maxDownloads) - taskCount;
while (newTaskCount-- > 0)
{
Interlocked.Increment(ref threadCount);
Interlocked.Increment(ref taskCount);
ThreadPool.QueueUserWorkItem(o => LoadPendingTiles(dispatcher, tileSource, sourceName, animateOpacity));
Task.Run(() => LoadPendingTiles(dispatcher, tileSource, sourceName));
}
}
private void LoadPendingTiles(Dispatcher dispatcher, TileSource tileSource, string sourceName, bool animateOpacity)
private void LoadPendingTiles(Dispatcher dispatcher, TileSource tileSource, string sourceName)
{
var setImageAction = new Action<Tile, ImageSource>((t, i) => t.SetImageSource(i, animateOpacity));
var imageTileSource = tileSource as ImageTileSource;
Tile tile;
PendingTile pendingTile;
while (pendingTiles.TryDequeue(out tile))
while (pendingTiles.TryDequeue(out pendingTile))
{
var tile = pendingTile.Tile;
ImageSource image = null;
if (imageTileSource != null)
@ -161,29 +166,33 @@ namespace MapControl
}
else
{
DateTime expirationTime;
var buffer = DownloadImage(uri, out expirationTime);
HttpStatusCode statusCode;
image = CreateImage(buffer);
image = DownloadImage(uri, GetCacheKey(sourceName, tile), out statusCode);
if (image != null &&
Cache != null &&
!string.IsNullOrWhiteSpace(sourceName) &&
expirationTime > DateTime.UtcNow)
if (statusCode == HttpStatusCode.NotFound)
{
Cache.Set(TileCache.Key(sourceName, tile), buffer, new CacheItemPolicy { AbsoluteExpiration = expirationTime });
tileSource.IgnoreTile(tile.XIndex, tile.Y, tile.ZoomLevel); // do not request again
}
}
}
}
if (image != null || !tile.HasImageSource) // set null image if tile does not yet have an ImageSource
else if (image == null) // download failed, use cached image if available
{
dispatcher.BeginInvoke(setImageAction, tile, image);
image = pendingTile.CachedImage;
}
}
}
}
Interlocked.Decrement(ref threadCount);
if (image != null)
{
dispatcher.BeginInvoke(new Action<Tile, ImageSource>((t, i) => t.SetImage(i)), tile, image);
}
else
{
tile.SetImage(null);
}
}
Interlocked.Decrement(ref taskCount);
}
private static ImageSource LoadImage(ImageTileSource tileSource, Tile tile)
@ -196,7 +205,7 @@ namespace MapControl
}
catch (Exception ex)
{
Trace.TraceWarning("Loading tile image failed: {0}", ex.Message);
Debug.WriteLine("Loading tile image failed: {0}", (object)ex.Message);
}
return image;
@ -210,122 +219,138 @@ namespace MapControl
{
try
{
using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
image = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
image = BitmapFrame.Create(fileStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
}
}
catch (Exception ex)
{
Trace.TraceWarning("Creating tile image failed: {0}", ex.Message);
Debug.WriteLine("Creating tile image failed: {0}", (object)ex.Message);
}
}
return image;
}
private static ImageSource CreateImage(byte[] buffer)
private static ImageSource DownloadImage(Uri uri, string cacheKey, out HttpStatusCode statusCode)
{
ImageSource image = null;
if (buffer != null)
{
try
{
using (var stream = TileCache.ImageStream(buffer))
{
image = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
}
}
catch (Exception ex)
{
Trace.TraceWarning("Creating tile image failed: {0}", ex.Message);
}
}
return image;
}
private static byte[] DownloadImage(Uri uri, out DateTime expirationTime)
{
expirationTime = DateTime.UtcNow + DefaultCacheExpiration;
byte[] buffer = null;
BitmapSource image = null;
statusCode = HttpStatusCode.Unused;
try
{
var request = HttpWebRequest.CreateHttp(uri);
request.UserAgent = HttpUserAgent;
using (var response = (HttpWebResponse)request.GetResponse())
{
statusCode = response.StatusCode;
using (var responseStream = response.GetResponseStream())
using (var memoryStream = new MemoryStream())
{
var expiresHeader = response.Headers["Expires"];
DateTime expires;
if (expiresHeader != null &&
DateTime.TryParse(expiresHeader, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out expires) &&
expirationTime > expires)
{
expirationTime = expires;
responseStream.CopyTo(memoryStream);
memoryStream.Position = 0;
image = BitmapFrame.Create(memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
}
buffer = TileCache.CreateBuffer(responseStream, (int)response.ContentLength, expirationTime);
if (cacheKey != null)
{
SetCachedImage(cacheKey, image, GetExpiration(response.Headers));
}
}
//Trace.TraceInformation("Downloaded {0}, expires {1}", uri, expirationTime);
}
catch (WebException ex)
{
if (ex.Status == WebExceptionStatus.ProtocolError)
var response = ex.Response as HttpWebResponse;
if (response != null)
{
var statusCode = ((HttpWebResponse)ex.Response).StatusCode;
if (statusCode != HttpStatusCode.NotFound)
{
Trace.TraceWarning("Downloading {0} failed: {1}", uri, ex.Message);
}
}
else
{
Trace.TraceWarning("Downloading {0} failed with {1}: {2}", uri, ex.Status, ex.Message);
statusCode = response.StatusCode;
}
Debug.WriteLine("Downloading {0} failed: {1}: {2}", uri, ex.Status, ex.Message);
}
catch (Exception ex)
{
Trace.TraceWarning("Downloading {0} failed: {1}", uri, ex.Message);
Debug.WriteLine("Downloading {0} failed: {1}", uri, ex.Message);
}
return buffer;
return image;
}
private static class TileCache
private static string GetCacheKey(string sourceName, Tile tile)
{
private const int imageBufferOffset = sizeof(Int64);
if (Cache == null || string.IsNullOrWhiteSpace(sourceName))
{
return null;
}
public static string Key(string sourceName, Tile tile)
{
return string.Format("{0}/{1}/{2}/{3}", sourceName, tile.ZoomLevel, tile.XIndex, tile.Y);
}
public static MemoryStream ImageStream(byte[] cacheBuffer)
private static bool GetCachedImage(string cacheKey, out BitmapSource image)
{
return new MemoryStream(cacheBuffer, imageBufferOffset, cacheBuffer.Length - imageBufferOffset, false);
image = Cache.Get(cacheKey) as BitmapSource;
if (image == null)
{
return false;
}
public static bool IsExpired(byte[] cacheBuffer)
{
return DateTime.FromBinary(BitConverter.ToInt64(cacheBuffer, 0)) < DateTime.UtcNow;
var metadata = (BitmapMetadata)image.Metadata;
DateTime expiration;
// get cache expiration date from BitmapMetadata.DateTaken, must be parsed with CurrentCulture
return metadata == null
|| metadata.DateTaken == null
|| !DateTime.TryParse(metadata.DateTaken, CultureInfo.CurrentCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out expiration)
|| expiration > DateTime.UtcNow;
}
public static byte[] CreateBuffer(Stream imageStream, int length, DateTime expirationTime)
private static void SetCachedImage(string cacheKey, BitmapSource image, DateTime expiration)
{
using (var memoryStream = length > 0 ? new MemoryStream(length + imageBufferOffset) : new MemoryStream())
{
memoryStream.Write(BitConverter.GetBytes(expirationTime.ToBinary()), 0, imageBufferOffset);
imageStream.CopyTo(memoryStream);
var bitmap = BitmapFrame.Create(image);
var metadata = (BitmapMetadata)bitmap.Metadata;
return length > 0 ? memoryStream.GetBuffer() : memoryStream.ToArray();
}
}
// store cache expiration date in BitmapMetadata.DateTaken
metadata.DateTaken = expiration.ToString(CultureInfo.InvariantCulture);
metadata.Freeze();
bitmap.Freeze();
Cache.Set(cacheKey, bitmap, new CacheItemPolicy { AbsoluteExpiration = expiration });
//Debug.WriteLine("Cached {0}, Expires {1}", cacheKey, expiration);
}
private static DateTime GetExpiration(WebHeaderCollection headers)
{
var cacheControl = headers["Cache-Control"];
int maxAge;
DateTime expiration;
if (cacheControl != null &&
cacheControl.StartsWith("max-age=") &&
int.TryParse(cacheControl.Substring(8), out maxAge))
{
maxAge = Math.Min(maxAge, (int)DefaultCacheExpiration.TotalSeconds);
expiration = DateTime.UtcNow.AddSeconds(maxAge);
}
else
{
var expires = headers["Expires"];
var maxExpiration = DateTime.UtcNow.Add(DefaultCacheExpiration);
if (expires == null ||
!DateTime.TryParse(expires, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out expiration) ||
expiration > maxExpiration)
{
expiration = maxExpiration;
}
}
return expiration;
}
}
}

View file

@ -3,14 +3,15 @@
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI.Core;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
using Windows.Web.Http;
using Windows.Web.Http.Filters;
@ -18,142 +19,180 @@ using Windows.Web.Http.Filters;
namespace MapControl
{
/// <summary>
/// Loads map tile images.
/// Loads map tile images and optionally caches them in a IImageCache.
/// </summary>
public class TileImageLoader : ITileImageLoader
{
public static IObjectCache Cache { get; set; }
/// <summary>
/// Default name of an IImageCache instance that is assigned to the Cache property.
/// </summary>
public const string DefaultCacheName = "TileCache";
private HttpClient httpClient;
/// <summary>
/// Default StorageFolder where an IImageCache instance may save cached data.
/// </summary>
public static readonly StorageFolder DefaultCacheFolder = ApplicationData.Current.TemporaryFolder;
/// <summary>
/// Default expiration time for cached tile images. Used when no expiration time
/// was transmitted on download. The default and recommended minimum value is seven days.
/// See OpenStreetMap tile usage policy: http://wiki.openstreetmap.org/wiki/Tile_usage_policy
/// </summary>
public static TimeSpan DefaultCacheExpiration = TimeSpan.FromDays(7);
/// <summary>
/// The IImageCache implementation used to cache tile images. The default is null.
/// </summary>
public static Caching.IImageCache Cache;
private class PendingTile
{
public readonly Tile Tile;
public readonly Uri Uri;
public readonly BitmapSource Image;
public PendingTile(Tile tile, Uri uri)
{
Tile = tile;
Uri = uri;
Image = new BitmapImage();
}
}
private readonly ConcurrentQueue<PendingTile> pendingTiles = new ConcurrentQueue<PendingTile>();
private int taskCount;
public void BeginLoadTiles(TileLayer tileLayer, IEnumerable<Tile> tiles)
{
var imageTileSource = tileLayer.TileSource as ImageTileSource;
var tileSource = tileLayer.TileSource;
var imageTileSource = tileSource as ImageTileSource;
var sourceName = tileLayer.SourceName;
var useCache = Cache != null && !string.IsNullOrEmpty(sourceName);
foreach (var tile in tiles)
{
try
{
ImageSource image = null;
if (imageTileSource != null)
{
image = imageTileSource.LoadImage(tile.XIndex, tile.Y, tile.ZoomLevel);
tile.SetImage(imageTileSource.LoadImage(tile.XIndex, tile.Y, tile.ZoomLevel));
}
else
{
var uri = tileLayer.TileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel);
var uri = tileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel);
if (uri != null)
if (uri == null)
{
if (Cache == null || string.IsNullOrEmpty(tileLayer.SourceName))
tile.SetImage(null);
}
else if (!useCache)
{
image = new BitmapImage(uri);
tile.SetImage(new BitmapImage(uri));
}
else
{
var bitmap = new BitmapImage();
image = bitmap;
Task.Run(async () => await LoadCachedImage(tileLayer, tile, uri, bitmap));
pendingTiles.Enqueue(new PendingTile(tile, uri));
}
}
}
tile.SetImageSource(image, tileLayer.AnimateTileOpacity);
}
catch (Exception ex)
{
Debug.WriteLine("Loading tile image failed: {0}", ex.Message);
}
var newTaskCount = Math.Min(pendingTiles.Count, tileLayer.MaxParallelDownloads) - taskCount;
while (newTaskCount-- > 0)
{
Interlocked.Increment(ref taskCount);
Task.Run(async () => await LoadPendingTiles(tileSource, sourceName));
}
}
}
public void CancelLoadTiles(TileLayer tileLayer)
{
PendingTile pendingTile;
while (pendingTiles.TryDequeue(out pendingTile)) ; // no Clear method
}
private async Task LoadCachedImage(TileLayer tileLayer, Tile tile, Uri uri, BitmapImage bitmap)
private async Task LoadPendingTiles(TileSource tileSource, string sourceName)
{
var cacheKey = string.Format(@"{0}\{1}\{2}\{3}{4}",
tileLayer.SourceName, tile.ZoomLevel, tile.XIndex, tile.Y, Path.GetExtension(uri.LocalPath));
PendingTile pendingTile;
var buffer = await Cache.GetAsync(cacheKey) as IBuffer;
if (buffer != null)
while (pendingTiles.TryDequeue(out pendingTile))
{
await LoadImageFromBuffer(buffer, bitmap);
//Debug.WriteLine("Loaded cached image {0}", cacheKey);
var tile = pendingTile.Tile;
var image = pendingTile.Image;
var uri = pendingTile.Uri;
var extension = Path.GetExtension(uri.LocalPath);
if (string.IsNullOrEmpty(extension) || extension == ".jpeg")
{
extension = ".jpg";
}
var cacheKey = string.Format(@"{0}\{1}\{2}\{3}{4}", sourceName, tile.ZoomLevel, tile.XIndex, tile.Y, extension);
var cacheItem = await Cache.GetAsync(cacheKey);
var cachedBuffer = cacheItem != null ? cacheItem.Buffer : null;
if (cachedBuffer != null && cacheItem.Expires > DateTime.UtcNow)
{
await LoadImageFromBuffer(tile, image, cachedBuffer);
}
else
{
DownloadAndCacheImage(uri, bitmap, cacheKey);
var statusCode = await DownloadImage(tile, image, uri, cacheKey, cachedBuffer);
if (statusCode == HttpStatusCode.NotFound)
{
tileSource.IgnoreTile(tile.XIndex, tile.Y, tile.ZoomLevel); // do not request again
}
}
}
private async Task LoadImageFromBuffer(IBuffer buffer, BitmapImage bitmap)
{
using (var stream = new InMemoryRandomAccessStream())
{
await stream.WriteAsync(buffer);
await stream.FlushAsync();
stream.Seek(0);
Interlocked.Decrement(ref taskCount);
}
await bitmap.Dispatcher.RunAsync(CoreDispatcherPriority.Low, async () =>
private async Task<HttpStatusCode> DownloadImage(Tile tile, BitmapSource image, Uri uri, string cacheKey, IBuffer cachedBuffer)
{
HttpStatusCode result = HttpStatusCode.None;
try
{
await bitmap.SetSourceAsync(stream);
}
catch (Exception ex)
using (var httpClient = new HttpClient(new HttpBaseProtocolFilter { AllowAutoRedirect = false }))
using (var response = await httpClient.GetAsync(uri))
{
Debug.WriteLine(ex.Message);
}
});
}
result = response.StatusCode;
if (response.IsSuccessStatusCode)
{
await LoadImageFromHttpResponse(response, tile, image, cacheKey);
return result;
}
private void DownloadAndCacheImage(Uri uri, BitmapImage bitmap, string cacheKey)
{
try
{
if (httpClient == null)
{
var filter = new HttpBaseProtocolFilter();
filter.AllowAutoRedirect = false;
filter.CacheControl.ReadBehavior = HttpCacheReadBehavior.Default;
filter.CacheControl.WriteBehavior = HttpCacheWriteBehavior.NoCache;
httpClient = new HttpClient(filter);
Debug.WriteLine("{0}: ({1}) {2}", uri, (int)response.StatusCode, response.ReasonPhrase);
}
httpClient.GetAsync(uri).Completed = async (request, status) =>
{
if (status == AsyncStatus.Completed)
{
using (var response = request.GetResults())
{
await LoadImageFromHttpResponse(response, bitmap, cacheKey);
}
}
else
{
Debug.WriteLine("{0}: {1}", uri, request.ErrorCode != null ? request.ErrorCode.Message : status.ToString());
}
};
}
catch (Exception ex)
{
Debug.WriteLine("{0}: {1}", uri, ex.Message);
}
if (cachedBuffer != null)
{
await LoadImageFromBuffer(tile, image, cachedBuffer);
}
private async Task LoadImageFromHttpResponse(HttpResponseMessage response, BitmapImage bitmap, string cacheKey)
{
if (response.IsSuccessStatusCode)
{
var stream = new InMemoryRandomAccessStream();
return result;
}
private async Task LoadImageFromHttpResponse(HttpResponseMessage response, Tile tile, BitmapSource image, string cacheKey)
{
using (var stream = new InMemoryRandomAccessStream())
{
using (var content = response.Content)
{
await content.WriteToStreamAsync(stream);
@ -162,35 +201,65 @@ namespace MapControl
await stream.FlushAsync();
stream.Seek(0);
await bitmap.Dispatcher.RunAsync(CoreDispatcherPriority.Low, async () =>
{
try
{
await bitmap.SetSourceAsync(stream);
var loaded = await LoadImageFromStream(tile, image, stream);
// cache image asynchronously, after successful decoding
var task = Task.Run(async () =>
if (loaded && cacheKey != null)
{
var buffer = new Windows.Storage.Streams.Buffer((uint)stream.Size);
stream.Seek(0);
await stream.ReadAsync(buffer, buffer.Capacity, InputStreamOptions.None);
stream.Dispose();
await Cache.SetAsync(cacheKey, buffer);
});
var maxAge = DefaultCacheExpiration;
if (response.Headers.CacheControl.MaxAge.HasValue &&
response.Headers.CacheControl.MaxAge.Value < maxAge)
{
maxAge = response.Headers.CacheControl.MaxAge.Value;
}
await Cache.SetAsync(cacheKey, buffer, DateTime.UtcNow.Add(maxAge));
}
}
}
private async Task<bool> LoadImageFromBuffer(Tile tile, BitmapSource image, IBuffer buffer)
{
using (var stream = new InMemoryRandomAccessStream())
{
await stream.WriteAsync(buffer);
await stream.FlushAsync();
stream.Seek(0);
return await LoadImageFromStream(tile, image, stream);
}
}
private async Task<bool> LoadImageFromStream(Tile tile, BitmapSource image, IRandomAccessStream stream)
{
var completion = new TaskCompletionSource<bool>();
var action = image.Dispatcher.RunAsync(
CoreDispatcherPriority.Normal,
async () =>
{
try
{
await image.SetSourceAsync(stream);
tile.SetImage(image, true, false);
completion.SetResult(true);
}
catch (Exception ex)
{
Debug.WriteLine("{0}: {1}", response.RequestMessage.RequestUri, ex.Message);
stream.Dispose();
Debug.WriteLine(ex.Message);
tile.SetImage(null);
completion.SetResult(false);
}
});
}
else
{
Debug.WriteLine("{0}: {1}", response.RequestMessage.RequestUri, response.StatusCode);
}
return await completion.Task;
}
}
}

View file

@ -2,13 +2,14 @@
// Copyright © 2014 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System.Threading.Tasks;
namespace MapControl
{
public interface IObjectCache
public partial class TileLayer
{
Task<object> GetAsync(string key);
Task SetAsync(string key, object value);
partial void Initialize()
{
IsHitTestVisible = false;
MapPanel.AddParentMapHandlers(this);
}
}
}

View file

@ -0,0 +1,17 @@
// XAML Map Control - http://xamlmapcontrol.codeplex.com/
// Copyright © 2014 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System.Windows;
namespace MapControl
{
public partial class TileLayer
{
static TileLayer()
{
UIElement.IsHitTestVisibleProperty.OverrideMetadata(
typeof(TileLayer), new FrameworkPropertyMetadata(false));
}
}
}

View file

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Linq;
#if WINDOWS_RUNTIME
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Documents;
using Windows.UI.Xaml.Markup;
using Windows.UI.Xaml.Media;
@ -19,12 +20,6 @@ using System.Windows.Media;
namespace MapControl
{
public interface ITileImageLoader
{
void BeginLoadTiles(TileLayer tileLayer, IEnumerable<Tile> tiles);
void CancelLoadTiles(TileLayer tileLayer);
}
/// <summary>
/// Fills a rectangular area with map tiles from a TileSource.
/// </summary>
@ -33,7 +28,7 @@ namespace MapControl
#else
[ContentProperty("TileSource")]
#endif
public class TileLayer : PanelBase
public partial class TileLayer : PanelBase, IMapElement
{
public static TileLayer Default
{
@ -48,11 +43,34 @@ namespace MapControl
}
}
public static readonly DependencyProperty TileSourceProperty = DependencyProperty.Register(
"TileSource", typeof(TileSource), typeof(TileLayer),
new PropertyMetadata(null, (o, e) => ((TileLayer)o).UpdateTiles()));
public static readonly DependencyProperty SourceNameProperty = DependencyProperty.Register(
"SourceName", typeof(string), typeof(TileLayer), new PropertyMetadata(null));
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(
"Description", typeof(string), typeof(TileLayer), new PropertyMetadata(null));
public static readonly DependencyProperty MinZoomLevelProperty = DependencyProperty.Register(
"MinZoomLevel", typeof(int), typeof(TileLayer), new PropertyMetadata(0));
public static readonly DependencyProperty MaxZoomLevelProperty = DependencyProperty.Register(
"MaxZoomLevel", typeof(int), typeof(TileLayer), new PropertyMetadata(18));
public static readonly DependencyProperty MaxParallelDownloadsProperty = DependencyProperty.Register(
"MaxParallelDownloads", typeof(int), typeof(TileLayer), new PropertyMetadata(4));
public static readonly DependencyProperty ForegroundProperty = DependencyProperty.Register(
"Foreground", typeof(Brush), typeof(TileLayer), new PropertyMetadata(null));
public static readonly new DependencyProperty BackgroundProperty = DependencyProperty.Register(
"Background", typeof(Brush), typeof(TileLayer), new PropertyMetadata(null));
private readonly ITileImageLoader tileImageLoader;
private TileSource tileSource;
private List<Tile> tiles = new List<Tile>();
private int zoomLevel;
private Int32Rect grid;
private MapBase parentMap;
public TileLayer()
: this(new TileImageLoader())
@ -62,94 +80,160 @@ namespace MapControl
public TileLayer(ITileImageLoader tileImageLoader)
{
this.tileImageLoader = tileImageLoader;
MinZoomLevel = 0;
MaxZoomLevel = 18;
MaxParallelDownloads = 4;
LoadLowerZoomLevels = true;
AnimateTileOpacity = true;
Initialize();
}
public string SourceName { get; set; }
public string Description { get; set; }
public int MinZoomLevel { get; set; }
public int MaxZoomLevel { get; set; }
public int MaxParallelDownloads { get; set; }
public bool LoadLowerZoomLevels { get; set; }
public bool AnimateTileOpacity { get; set; }
public Brush Foreground { get; set; }
partial void Initialize();
/// <summary>
/// Controls how map tiles are loaded.
/// </summary>
public TileSource TileSource
{
get { return (TileSource)GetValue(TileSourceProperty); }
set { SetValue(TileSourceProperty, value); }
}
/// <summary>
/// Name of the TileSource. Used as key in a TileLayerCollection and to name an optional tile cache.
/// </summary>
public string SourceName
{
get { return (string)GetValue(SourceNameProperty); }
set { SetValue(SourceNameProperty, value); }
}
/// <summary>
/// Description of the TileLayer.
/// </summary>
public string Description
{
get { return (string)GetValue(DescriptionProperty); }
set { SetValue(DescriptionProperty, value); }
}
/// <summary>
/// Minimum zoom level supported by the TileLayer.
/// </summary>
public int MinZoomLevel
{
get { return (int)GetValue(MinZoomLevelProperty); }
set { SetValue(MinZoomLevelProperty, value); }
}
/// <summary>
/// Maximum zoom level supported by the TileLayer.
/// </summary>
public int MaxZoomLevel
{
get { return (int)GetValue(MaxZoomLevelProperty); }
set { SetValue(MaxZoomLevelProperty, value); }
}
/// <summary>
/// Maximum number of parallel downloads that may be performed by the TileLayer's ITileImageLoader.
/// </summary>
public int MaxParallelDownloads
{
get { return (int)GetValue(MaxParallelDownloadsProperty); }
set { SetValue(MaxParallelDownloadsProperty, value); }
}
/// <summary>
/// Sets MapBase.Foreground, if not null.
/// </summary>
public Brush Foreground
{
get { return (Brush)GetValue(ForegroundProperty); }
set { SetValue(ForegroundProperty, value); }
}
/// <summary>
/// Sets MapBase.Background, if not null.
/// New property prevents filling of RenderTransformed TileLayer with Panel.Background.
/// </summary>
public new Brush Background
{
get { return (Brush)GetValue(BackgroundProperty); }
set { SetValue(BackgroundProperty, value); }
}
/// <summary>
/// In case the Description text contains copyright links in markdown syntax [text](url),
/// the DescriptionInlines property may be used to create a collection of Run and Hyperlink
/// inlines to be displayed in e.g. a TextBlock or a Silverlight RichTextBlock.
/// </summary>
public ICollection<Inline> DescriptionInlines
public List<Inline> DescriptionInlines
{
get { return Description.ToInlines(); }
}
public TileSource TileSource
public MapBase ParentMap
{
get { return tileSource; }
get { return parentMap; }
set
{
tileSource = value;
if (grid.Width > 0 && grid.Height > 0)
if (parentMap != null)
{
ClearTiles();
parentMap.TileGridChanged -= UpdateTiles;
ClearValue(RenderTransformProperty);
}
parentMap = value;
if (parentMap != null)
{
parentMap.TileGridChanged += UpdateTiles;
RenderTransform = parentMap.TileLayerTransform;
}
UpdateTiles();
}
}
}
internal void ClearTiles()
protected virtual void UpdateTiles()
{
if (tiles.Count > 0)
{
tileImageLoader.CancelLoadTiles(this);
tiles.Clear();
Children.Clear();
}
internal void UpdateTiles(int zoomLevel, Int32Rect grid)
{
this.zoomLevel = zoomLevel;
this.grid = grid;
UpdateTiles();
}
private void UpdateTiles()
{
if (tileSource != null)
{
tileImageLoader.CancelLoadTiles(this);
SelectTiles();
Children.Clear();
if (tiles.Count > 0)
{
foreach (var tile in tiles)
{
Children.Add(tile.Image);
}
tileImageLoader.BeginLoadTiles(this, tiles.Where(t => !t.HasImageSource));
tileImageLoader.BeginLoadTiles(this, tiles.Where(t => t.Pending));
}
}
private void UpdateTiles(object sender, EventArgs e)
{
UpdateTiles();
}
private void SelectTiles()
{
var maxZoomLevel = Math.Min(zoomLevel, MaxZoomLevel);
var minZoomLevel = maxZoomLevel;
if (LoadLowerZoomLevels &&
Parent is TileContainer &&
((TileContainer)Parent).TileLayers.FirstOrDefault() == this)
{
minZoomLevel = MinZoomLevel;
}
var newTiles = new List<Tile>();
if (parentMap != null && TileSource != null)
{
var grid = parentMap.TileGrid;
var zoomLevel = parentMap.TileZoomLevel;
var maxZoomLevel = Math.Min(zoomLevel, MaxZoomLevel);
var minZoomLevel = MinZoomLevel;
if (parentMap.TileLayers.FirstOrDefault() != this)
{
// do not load background tiles if this is not the base layer
minZoomLevel = Math.Max(maxZoomLevel, minZoomLevel);
}
for (var z = minZoomLevel; z <= maxZoomLevel; z++)
{
var tileSize = 1 << (zoomLevel - z);
@ -173,8 +257,8 @@ namespace MapControl
if (equivalentTile != null)
{
// do not animate to avoid flicker when crossing date line
tile.SetImageSource(equivalentTile.Image.Source, false);
// do not animate to avoid flicker when crossing 180°
tile.SetImage(equivalentTile.Image.Source, false);
}
}
@ -182,18 +266,25 @@ namespace MapControl
}
}
}
}
tiles = newTiles;
}
protected override Size ArrangeOverride(Size finalSize)
{
if (parentMap != null)
{
foreach (var tile in tiles)
{
var tileSize = (double)(256 << (zoomLevel - tile.ZoomLevel));
var tileSize = (double)(256 << (parentMap.TileZoomLevel - tile.ZoomLevel));
var x = tileSize * tile.X - 256 * parentMap.TileGrid.X;
var y = tileSize * tile.Y - 256 * parentMap.TileGrid.Y;
tile.Image.Width = tileSize;
tile.Image.Height = tileSize;
tile.Image.Arrange(new Rect(tileSize * tile.X - 256 * grid.X, tileSize * tile.Y - 256 * grid.Y, tileSize, tileSize));
tile.Image.Arrange(new Rect(x, y, tileSize, tileSize));
}
}
return finalSize;

View file

@ -3,6 +3,7 @@
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Collections.Generic;
using System.Globalization;
#if WINDOWS_RUNTIME
using Windows.Foundation;
@ -20,6 +21,8 @@ namespace MapControl
public const int TileSize = 256;
public const double MetersPerDegree = 6378137d * Math.PI / 180d; // WGS 84 semi major axis
protected readonly HashSet<long> IgnoredTiles = new HashSet<long>();
private Func<int, int, int, Uri> getUri;
private string uriFormat = string.Empty;
@ -84,7 +87,38 @@ namespace MapControl
public virtual Uri GetUri(int x, int y, int zoomLevel)
{
return getUri != null ? getUri(x, y, zoomLevel) : null;
if (getUri == null)
{
return null;
}
if (IgnoredTiles.Count > 0)
{
lock (IgnoredTiles)
{
if (IgnoredTiles.Contains(GetHashCode(x, y, zoomLevel)))
{
return null;
}
}
}
return getUri(x, y, zoomLevel);
}
public void IgnoreTile(int x, int y, int zoomLevel)
{
lock (IgnoredTiles)
{
IgnoredTiles.Add(GetHashCode(x, y, zoomLevel));
}
}
protected static long GetHashCode(int x, int y, int zoomLevel)
{
return (long)(x & 0xFFFFFF)
+ ((long)(y & 0xFFFFFF) << 24)
+ ((long)(zoomLevel & 0xFF) << 48);
}
private Uri GetBasicUri(int x, int y, int zoomLevel)

View file

@ -48,8 +48,8 @@
<Compile Include="..\HyperlinkText.cs">
<Link>HyperlinkText.cs</Link>
</Compile>
<Compile Include="..\ImageFileCache.WinRT.cs">
<Link>ImageFileCache.WinRT.cs</Link>
<Compile Include="..\ImageCache.WinRT.cs">
<Link>ImageCache.WinRT.cs</Link>
</Compile>
<Compile Include="..\ImageTileSource.Silverlight.WinRT.cs">
<Link>ImageTileSource.Silverlight.WinRT.cs</Link>
@ -60,8 +60,8 @@
<Compile Include="..\Int32Rect.cs">
<Link>Int32Rect.cs</Link>
</Compile>
<Compile Include="..\IObjectCache.WinRT.cs">
<Link>IObjectCache.WinRT.cs</Link>
<Compile Include="..\ITileImageLoader.cs">
<Link>ITileImageLoader.cs</Link>
</Compile>
<Compile Include="..\Location.cs">
<Link>Location.cs</Link>
@ -144,27 +144,21 @@
<Compile Include="..\Pushpin.Silverlight.WinRT.cs">
<Link>Pushpin.Silverlight.WinRT.cs</Link>
</Compile>
<Compile Include="..\Settings.cs">
<Link>Settings.cs</Link>
</Compile>
<Compile Include="..\Tile.cs">
<Link>Tile.cs</Link>
</Compile>
<Compile Include="..\Tile.Silverlight.WinRT.cs">
<Link>Tile.Silverlight.WinRT.cs</Link>
</Compile>
<Compile Include="..\TileContainer.cs">
<Link>TileContainer.cs</Link>
</Compile>
<Compile Include="..\TileContainer.Silverlight.WinRT.cs">
<Link>TileContainer.Silverlight.WinRT.cs</Link>
</Compile>
<Compile Include="..\TileImageLoader.WinRT.cs">
<Link>TileImageLoader.WinRT.cs</Link>
</Compile>
<Compile Include="..\TileLayer.cs">
<Link>TileLayer.cs</Link>
</Compile>
<Compile Include="..\TileLayer.Silverlight.WinRT.cs">
<Link>TileLayer.Silverlight.WinRT.cs</Link>
</Compile>
<Compile Include="..\TileLayerCollection.cs">
<Link>TileLayerCollection.cs</Link>
</Compile>

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.3.1")]
[assembly: AssemblyFileVersion("2.3.1")]
[assembly: AssemblyVersion("2.4.0")]
[assembly: AssemblyFileVersion("2.4.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]

View file

@ -32,7 +32,8 @@
<map:TileSource UriFormat="http://{c}.tile.thunderforest.com/transport/{z}/{x}/{y}.png"/>
</map:TileLayer>
<map:TileLayer SourceName="Thunderforest Transport Dark"
Description="Maps © [Thunderforest](http://www.thunderforest.com/), Data © [OpenStreetMap Contributors](http://www.openstreetmap.org/copyright)">
Description="Maps © [Thunderforest](http://www.thunderforest.com/), Data © [OpenStreetMap Contributors](http://www.openstreetmap.org/copyright)"
Foreground="White" Background="Black">
<map:TileSource UriFormat="http://{c}.tile.thunderforest.com/transport-dark/{z}/{x}/{y}.png"/>
</map:TileLayer>
<map:TileLayer SourceName="MapQuest OpenStreetMap"

View file

@ -15,8 +15,7 @@ namespace PhoneApplication
public MainPage()
{
//TileImageLoader.Cache = new ImageFileCache();
//BingMapsTileLayer.ApiKey = ...
//BingMapsTileLayer.ApiKey = "...";
InitializeComponent();

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/2010/manifest" xmlns:m2="http://schemas.microsoft.com/appx/2013/manifest" xmlns:m3="http://schemas.microsoft.com/appx/2014/manifest" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest">
<Identity Name="45fd5832-8021-45d0-909e-dd7af206f388" Publisher="CN=Clemens" Version="1.0.0.0" />
<mp:PhoneIdentity PhoneProductId="45fd5832-8021-45d0-909e-dd7af206f388" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
<Identity Name="XamlMapControl.PhoneApp" Publisher="CN=Clemens" Version="2.4.0.0" />
<mp:PhoneIdentity PhoneProductId="a28a99bb-a24b-4713-a6ea-3015d8aa2d72" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
<Properties>
<DisplayName>PhoneApplication</DisplayName>
<DisplayName>XAML Map Control Phone Application</DisplayName>
<PublisherDisplayName>Clemens</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
@ -16,7 +16,7 @@
</Resources>
<Applications>
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="PhoneApplication.App">
<m3:VisualElements DisplayName="PhoneApplication" Square150x150Logo="Assets\Logo.png" Square44x44Logo="Assets\SmallLogo.png" Description="PhoneApplication" ForegroundText="light" BackgroundColor="transparent">
<m3:VisualElements DisplayName="XAML Map Control Test Application" Square150x150Logo="Assets\Logo.png" Square44x44Logo="Assets\SmallLogo.png" Description="PhoneApplication" ForegroundText="light" BackgroundColor="transparent">
<m3:DefaultTile Wide310x150Logo="Assets\WideLogo.png" Square71x71Logo="Assets\Square71x71Logo.png">
</m3:DefaultTile>
<m3:SplashScreen Image="Assets\SplashScreen.png" />

View file

@ -14,6 +14,7 @@
<MinimumVisualStudioVersion>12</MinimumVisualStudioVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{76F1466A-8B6D-4E39-A767-685A06062A39};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<AppxBundle>Always</AppxBundle>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.3.1")]
[assembly: AssemblyFileVersion("2.3.1")]
[assembly: AssemblyVersion("2.4.0")]
[assembly: AssemblyFileVersion("2.4.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.3.1")]
[assembly: AssemblyFileVersion("2.3.1")]
[assembly: AssemblyVersion("2.4.0")]
[assembly: AssemblyFileVersion("2.4.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]

View file

@ -32,7 +32,7 @@
<map:TileLayer SourceName="Thunderforest Transport Dark"
Description="Maps © [Thunderforest](http://www.thunderforest.com/), Data © [OpenStreetMap Contributors](http://www.openstreetmap.org/copyright)"
TileSource="http://{c}.tile.thunderforest.com/transport-dark/{z}/{x}/{y}.png"
Background="Black" Foreground="White"/>
Foreground="White" Background="Black"/>
<map:TileLayer SourceName="MapQuest OpenStreetMap"
Description="Maps © [MapQuest](http://www.mapquest.com/), Data © [OpenStreetMap Contributors](http://www.openstreetmap.org/copyright)"
TileSource="http://otile{n}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png"
@ -159,11 +159,12 @@
</Path>
<map:Pushpin map:MapPanel.Location="53.5,8.2" Background="Yellow" Foreground="Blue" Content="N 53° 30' E 8° 12'"/>
</map:Map>
<Border HorizontalAlignment="Right" VerticalAlignment="Bottom" Background="#7FFFFFFF">
<RichTextBlock x:Name="mapLegend" Margin="4,2" FontSize="10"/>
</Border>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>

View file

@ -14,7 +14,7 @@ namespace SilverlightApplication
public MainPage()
{
//BingMapsTileLayer.ApiKey = ...
//BingMapsTileLayer.ApiKey = "...";
InitializeComponent();

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.3.1")]
[assembly: AssemblyFileVersion("2.3.1")]
[assembly: AssemblyVersion("2.4.0")]
[assembly: AssemblyFileVersion("2.4.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]

View file

@ -32,7 +32,8 @@
<map:TileSource UriFormat="http://{c}.tile.thunderforest.com/transport/{z}/{x}/{y}.png"/>
</map:TileLayer>
<map:TileLayer SourceName="Thunderforest Transport Dark"
Description="Maps © [Thunderforest](http://www.thunderforest.com/), Data © [OpenStreetMap Contributors](http://www.openstreetmap.org/copyright)">
Description="Maps © [Thunderforest](http://www.thunderforest.com/), Data © [OpenStreetMap Contributors](http://www.openstreetmap.org/copyright)"
Foreground="White" Background="Black">
<map:TileSource UriFormat="http://{c}.tile.thunderforest.com/transport-dark/{z}/{x}/{y}.png"/>
</map:TileLayer>
<map:TileLayer SourceName="MapQuest OpenStreetMap"

View file

@ -11,8 +11,9 @@ namespace StoreApplication
public MainPage()
{
//TileImageLoader.Cache = new ImageFileCache();
//BingMapsTileLayer.ApiKey = ...
//TileImageLoader.Cache = new MapControl.Caching.ImageFileCache();
//TileImageLoader.Cache = new MapControl.Caching.FileDbCache();
//BingMapsTileLayer.ApiKey = "...";
this.InitializeComponent();

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/2010/manifest" xmlns:m2="http://schemas.microsoft.com/appx/2013/manifest">
<Identity Name="199e6e5e-60fe-46b4-a699-07c78f31849c" Publisher="CN=Clemens" Version="1.1.0.0" />
<Identity Name="XamlMapControl.StoreApp" Publisher="CN=Clemens" Version="2.4.0.0" />
<Properties>
<DisplayName>StoreApplication</DisplayName>
<DisplayName>XAML Map Control Store Application</DisplayName>
<PublisherDisplayName>Clemens</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
@ -15,7 +15,7 @@
</Resources>
<Applications>
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="StoreApplication.App">
<m2:VisualElements DisplayName="StoreApplication" Description="StoreApplication" BackgroundColor="#464646" ForegroundText="light" Square150x150Logo="Assets\Logo.png" Square30x30Logo="Assets\SmallLogo.png">
<m2:VisualElements DisplayName="XAML Map Control Test Application" Description="StoreApplication" BackgroundColor="#464646" ForegroundText="light" Square150x150Logo="Assets\Logo.png" Square30x30Logo="Assets\SmallLogo.png">
<m2:DefaultTile>
<m2:ShowNameOnTiles>
<m2:ShowOn Tile="square150x150Logo" />

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.3.1")]
[assembly: AssemblyFileVersion("2.3.1")]
[assembly: AssemblyVersion("2.4.0")]
[assembly: AssemblyFileVersion("2.4.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]

View file

@ -16,6 +16,7 @@
<TargetPlatformVersion>8.1</TargetPlatformVersion>
<MinimumVisualStudioVersion>12</MinimumVisualStudioVersion>
<TargetFrameworkVersion />
<AppxBundle>Always</AppxBundle>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@ -78,6 +79,14 @@
</Page>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Caching\FileDbCache.WinRT\FileDbCache.WinRT.csproj">
<Project>{c7bf2b18-cc74-430b-bcb2-600304efa3d8}</Project>
<Name>FileDbCache.WinRT</Name>
</ProjectReference>
<ProjectReference Include="..\..\Caching\ImageFileCache.WinRT\ImageFileCache.WinRT.csproj">
<Project>{f789647e-96f7-43e3-a895-fa3fe8d01260}</Project>
<Name>ImageFileCache.WinRT</Name>
</ProjectReference>
<ProjectReference Include="..\..\MapControl\WinRT\MapControl.WinRT.csproj">
<Project>{63cefdf7-5170-43b6-86f8-5c4a383a1615}</Project>
<Name>MapControl.WinRT</Name>

View file

@ -1,17 +1,5 @@
<?xml version="1.0"?>
<configuration>
<configSections>
<sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<section name="WpfApplication.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</sectionGroup>
</configSections>
<applicationSettings>
<WpfApplication.Properties.Settings>
<setting name="TileCache" serializeAs="String">
<value />
</setting>
</WpfApplication.Properties.Settings>
</applicationSettings>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
</startup>

View file

@ -31,14 +31,14 @@
<map:TileLayer SourceName="Thunderforest Transport Dark"
Description="Maps © [Thunderforest](http://www.thunderforest.com/), Data © [OpenStreetMap Contributors](http://www.openstreetmap.org/copyright)"
TileSource="http://{c}.tile.thunderforest.com/transport-dark/{z}/{x}/{y}.png"
Background="Black" Foreground="White"/>
Foreground="White" Background="Black"/>
<map:TileLayer SourceName="MapQuest OpenStreetMap"
Description="Maps © [MapQuest](http://www.mapquest.com/), Data © [OpenStreetMap Contributors](http://www.openstreetmap.org/copyright)"
TileSource="http://otile{n}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png"
MaxZoomLevel="19"/>
<map:TileLayer SourceName="Seamarks"
TileSource="http://tiles.openseamap.org/seamark/{z}/{x}/{y}.png"
MinZoomLevel="10" MaxZoomLevel="18"/>
MinZoomLevel="9" MaxZoomLevel="18"/>
<!--
Bing Maps TileLayers with tile URLs retrieved from the Imagery Metadata Service
@ -66,7 +66,7 @@
<!--
A TileLayer that uses an ImageTileSource, which bypasses caching of map tile images
-->
<!--<map:TileLayer SourceName="OSM Uncached"
<!--<map:TileLayer SourceName="OpenStreetMap No Cache"
Description="© [OpenStreetMap Contributors](http://www.openstreetmap.org/copyright)">
<map:ImageTileSource IsAsync="True" UriFormat="http://{c}.tile.openstreetmap.org/{z}/{x}/{y}.png"/>
</map:TileLayer>-->
@ -189,7 +189,7 @@
Source="10_535_330.jpg" Opacity="0.5"/>
<map:MapGraticule Opacity="0.6"/>
<map:MapScale Margin="4" Opacity="0.8" HorizontalAlignment="Left"/>
<map:MapScale Margin="4" Opacity="0.8" HorizontalAlignment="Left" Background="Transparent"/>
<!-- use ItemTemplate or ItemContainerStyle alternatively -->
<map:MapItemsControl ItemsSource="{Binding Polylines}"
@ -206,6 +206,12 @@
ItemContainerStyle="{StaticResource PushpinItemStyle}"
IsSynchronizedWithCurrentItem="True"/>
<Path map:MapPanel.Location="53.5,8.2" Stroke="Blue" StrokeThickness="3" Fill="#1F007F00">
<Path.Data>
<EllipseGeometry RadiusX="1852" RadiusY="1852" Transform="{Binding ScaleTransform, ElementName=map}"/>
</Path.Data>
</Path>
<map:Pushpin map:MapPanel.Location="53.5,8.2" Background="Yellow" Foreground="Blue" Content="N 53° 30' E 8° 12'">
<map:Pushpin.Visibility>
<MultiBinding Converter="{StaticResource LocationToVisibilityConverter}">
@ -214,16 +220,12 @@
</MultiBinding>
</map:Pushpin.Visibility>
</map:Pushpin>
<Path map:MapPanel.Location="53.5,8.2" Stroke="Blue" StrokeThickness="3" HorizontalAlignment="Left" VerticalAlignment="Top">
<Path.Data>
<EllipseGeometry RadiusX="1852" RadiusY="1852" Transform="{Binding ScaleTransform, ElementName=map}"/>
</Path.Data>
</Path>
</map:Map>
<Border HorizontalAlignment="Right" VerticalAlignment="Bottom" Background="#7FFFFFFF">
<TextBlock x:Name="mapLegend" Margin="2" FontSize="10"/>
</Border>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>

View file

@ -1,11 +1,10 @@
using System;
using System.Globalization;
using System.Runtime.Caching;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Caching;
using MapControl;
using MapControl.Caching;
namespace WpfApplication
{
@ -15,25 +14,10 @@ namespace WpfApplication
public MainWindow()
{
switch (Properties.Settings.Default.TileCache)
{
case "MemoryCache":
TileImageLoader.Cache = MemoryCache.Default; // this is the default value of the TileImageLoader.Cache property
break;
case "ImageFileCache":
TileImageLoader.Cache = new ImageFileCache(TileImageLoader.DefaultCacheName, TileImageLoader.DefaultCacheDirectory);
break;
case "FileDbCache":
TileImageLoader.Cache = new FileDbCache(TileImageLoader.DefaultCacheName, TileImageLoader.DefaultCacheDirectory);
break;
case "None":
TileImageLoader.Cache = null;
break;
default:
break;
}
//BingMapsTileLayer.ApiKey = ...
//TileImageLoader.Cache = new ImageFileCache(TileImageLoader.DefaultCacheName, TileImageLoader.DefaultCacheFolder);
//TileImageLoader.Cache = new FileDbCache(TileImageLoader.DefaultCacheName, TileImageLoader.DefaultCacheFolder);
//TileImageLoader.HttpUserAgent = "...";
//BingMapsTileLayer.ApiKey = "...";
InitializeComponent();

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.3.1")]
[assembly: AssemblyFileVersion("2.3.1")]
[assembly: AssemblyVersion("2.4.0")]
[assembly: AssemblyFileVersion("2.4.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]

View file

@ -1,35 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.34014
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace WpfApplication.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
[global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
public string TileCache {
get {
return ((string)(this["TileCache"]));
}
}
}
}

View file

@ -1,9 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="WpfApplication.Properties" GeneratedClassName="Settings">
<Profiles />
<Settings>
<Setting Name="TileCache" Type="System.String" Scope="Application">
<Value Profile="(Default)" />
</Setting>
</Settings>
</SettingsFile>

View file

@ -23,6 +23,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@ -32,6 +33,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
@ -70,28 +72,19 @@
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<AppDesigner Include="Properties\" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Caching\FileDbCache\FileDbCache.csproj">
<ProjectReference Include="..\..\Caching\FileDbCache.WPF\FileDbCache.WPF.csproj">
<Project>{ef44f661-b98a-4676-927f-85d138f82300}</Project>
<Name>FileDbCache</Name>
<Name>FileDbCache.WPF</Name>
</ProjectReference>
<ProjectReference Include="..\..\Caching\ImageFileCache\ImageFileCache.csproj">
<ProjectReference Include="..\..\Caching\ImageFileCache.WPF\ImageFileCache.WPF.csproj">
<Project>{86470440-fee2-4120-af5a-3762fb9c536f}</Project>
<Name>ImageFileCache</Name>
<Name>ImageFileCache.WPF</Name>
</ProjectReference>
<ProjectReference Include="..\..\MapControl\MapControl.WPF.csproj">
<Project>{226f3575-b683-446d-a2f0-181291dc8787}</Project>