mirror of
https://github.com/ClemensFischer/XAML-Map-Control.git
synced 2025-12-06 07:12:04 +01:00
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:
parent
f04025067c
commit
3b6545e738
BIN
Caching/FileDb/FileDb.dll
Normal file
BIN
Caching/FileDb/FileDb.dll
Normal file
Binary file not shown.
1870
Caching/FileDb/FileDb.xml
Normal file
1870
Caching/FileDb/FileDb.xml
Normal file
File diff suppressed because it is too large
Load diff
BIN
Caching/FileDb/FileDbPcl.dll
Normal file
BIN
Caching/FileDb/FileDbPcl.dll
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load diff
983
Caching/FileDb/Help.html
Normal file
983
Caching/FileDb/Help.html
Normal 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. <u>FileDb is a No-SQL database</u>
|
||||
meant for use as a local data store for applications.
|
||||
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.
|
||||
So you can just as easily read/write a String[] field as
|
||||
you would an Int field. Another feature is that a
|
||||
database file created on any .NET platform will work on
|
||||
any other. 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 "flat-file" database, using
|
||||
LINQ it becomes fully relational! 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.
|
||||
(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 = "FirstName IN ('Cindy', 'John') AND Age > 32"</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. 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. This is much easier than using Sqlite
|
||||
wrappers. 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. 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 "all or nothing", 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.
|
||||
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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</li>
|
||||
</font>
|
||||
<b>
|
||||
<li><font face="Arial">Fields</font></b><font face="Arial">: A List of Field
|
||||
objects.<br>
|
||||
</font></li>
|
||||
<li>
|
||||
<font face="Arial">
|
||||
<b>
|
||||
Record</b>: A list of data objects represents a single row in a Table.
|
||||
Implements IEnumerable and the Data property which is used for DataBinding.<br>
|
||||
</li>
|
||||
</font>
|
||||
<b>
|
||||
<li><font face="Arial">Records</font></b><font face="Arial">: A List of
|
||||
Record objects.<br>
|
||||
</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>
|
||||
</li>
|
||||
</font>
|
||||
<b>
|
||||
<li><font face="Arial">FilterExpression</font></b><font face="Arial">: Used
|
||||
to filter records for query, update and delete.<br>
|
||||
</li>
|
||||
</font>
|
||||
<b>
|
||||
<li><font face="Arial">FilterExpressionGroup</font></b><font face="Arial">:
|
||||
Used to create compound expressions by grouping FilterExpressions and
|
||||
FilterExpressionGroups.<br>
|
||||
</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. You can say the
|
||||
"default" way is with the built-in Record and Records classes. Think of
|
||||
Record as the .NET DataRow class, and think of Table as a DataTable. 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" );<br>
|
||||
<br>
|
||||
Table employees = employeesDb.SelectAllRecords();<br>
|
||||
Record record =
|
||||
employees[0];<br>
|
||||
int id = (int) record["EmployeeId"];<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>
|
||||
where (string)
|
||||
e["FirstName"] == "John"<br>
|
||||
select e;</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</font>
|
||||
</blockquote>
|
||||
<p><font face="Arial">Notice we have to cast the record value to a string.
|
||||
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. But you can use your own custom classes
|
||||
if you want because FileDb has template (generic)
|
||||
overloads for each of the SelectRecords methods.
|
||||
You only need to create a class with public properties
|
||||
which match the names of the fields you want to use.
|
||||
Here'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>
|
||||
public int EmployeeID { get; set; }<br>
|
||||
public string LastName { get; set; }<br>
|
||||
|
||||
public string
|
||||
FirstName { get; set; }<br>
|
||||
|
||||
public string Title {
|
||||
get; set; }<br>
|
||||
|
||||
public string
|
||||
TitleOfCourtesy { get; set; }<br>
|
||||
|
||||
public DateTime
|
||||
BirthDate { get; set; }<br>
|
||||
|
||||
public DateTime
|
||||
HireDate { get; set; }<br>
|
||||
|
||||
public string Address
|
||||
{ get; set; }<br>
|
||||
|
||||
public string City {
|
||||
get; set; }<br>
|
||||
|
||||
public string Region {
|
||||
get; set; }<br>
|
||||
|
||||
public string
|
||||
PostalCode { get; set; }<br>
|
||||
|
||||
public string Country
|
||||
{ get; set; }<br>
|
||||
|
||||
public string
|
||||
HomePhone { get; set; }<br>
|
||||
|
||||
public string
|
||||
Extension { get; set; }<br>
|
||||
|
||||
public Byte[] Photo {
|
||||
get; set; }<br>
|
||||
|
||||
public string Notes {
|
||||
get; set; }<br>
|
||||
|
||||
public int ReportsTo {
|
||||
get; set; }<br>
|
||||
}</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</font>
|
||||
</blockquote>
|
||||
<p><font face="Arial">The templated SelectRecords versions return a IList<T>
|
||||
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<Employee>
|
||||
employees = employeesDb.SelectAllRecords<Employee>();<br>
|
||||
Employee employee
|
||||
= employees[0];<br>
|
||||
int id = Employee.EmployeeId;<br>
|
||||
<br>
|
||||
var emps = from e in
|
||||
employees<br>
|
||||
where e.FirstName
|
||||
== "John"<br>
|
||||
select e;</td>
|
||||
</tr>
|
||||
</table>
|
||||
</font>
|
||||
</blockquote>
|
||||
<p><font face="Arial">As you can see, this is much cleaner code. 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 =
|
||||
'value') 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( "LastName", "Peacock",
|
||||
Equality.Equal );<br>
|
||||
<br>
|
||||
// build the same expression using the
|
||||
parser<br>
|
||||
searchExp = FilterExpression.Parse( "LastName = 'Peacock'" ); <br>
|
||||
Table table = employeesDb.SelectRecords(
|
||||
searchExp,
|
||||
new string[] { "ID", "LastName" } );<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( "LastName
|
||||
= 'Peacock'", new string[] { "ID", "LastName"
|
||||
} );<br>
|
||||
<br>
|
||||
foreach( Record record in table )<br>
|
||||
{<br>
|
||||
foreach( object value in record )<br>
|
||||
{<br>
|
||||
|
||||
Debug.WriteLine( value );<br>
|
||||
|
||||
}<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( "(FirstName ~= 'andrew' OR ~FirstName = 'nancy')
|
||||
AND LastName = 'Fuller'" );<br>
|
||||
Table table = employeesDb.SelectRecords( filterExpGrp );<br>
|
||||
<br>
|
||||
// equivalent building it manually<br>
|
||||
var fname1Exp = new FilterExpression( "FirstName", "andrew", Equality.Equal,
|
||||
MatchType.IgnoreCase );<br>
|
||||
var fname2Exp = new FilterExpression( "FirstName", "nancy", Equality.Equal,
|
||||
MatchType.IgnoreCase );<br>
|
||||
var lnameExp = new FilterExpression( "LastName", "Fuller", 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( "(FirstName ~= 'andrew' OR ~FirstName = 'nancy')
|
||||
AND LastName = 'Fuller'" );<br>
|
||||
</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"> </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"> </td>
|
||||
<font face="Courier New">
|
||||
<td>No-case equality - strings only</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font face="Courier New"><b><></b></font></td>
|
||||
</font><font face="Arial">
|
||||
<td width="18"> </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"> </td>
|
||||
<font face="Courier New">
|
||||
<td><font face="Arial">Not Equal (same as <>)</font></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font face="Courier New"><b>>=</b></font></td>
|
||||
</font><font face="Arial">
|
||||
<td width="18"> </td>
|
||||
<font face="Courier New">
|
||||
<td><font face="Arial">Greater than or Equal</font></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font face="Courier New"><b><=</b></font></td>
|
||||
</font><font face="Arial">
|
||||
<td width="18"> </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"> </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"> </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"> </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"> </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> </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.
|
||||
Internally, FileDb uses FilterExpressions to evaluate fields. You don't
|
||||
need to use them because you can pass in filter strings and they'll be parsed
|
||||
into FilterExpressions/FilterExpressionGroups for you. This is just to
|
||||
show you how can create them manually if you want to. 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( "(~FirstName = 'steven' OR [FirstName]
|
||||
REGEX 'NANCY') AND LastName = 'Fuller'" );<br>
|
||||
Table table = employeesDb.SelectRecords( filterExpGrp );<br>
|
||||
<br>
|
||||
// we can manually build the same FilterExpressionGroup<br>
|
||||
var fname1Exp = FilterExpression.Parse( "~FirstName = steven" );<br>
|
||||
var fname2Exp = new FilterExpression( "FirstName", "NANCY", Equality.Regex );<br>
|
||||
var lnameExp = new FilterExpression( "LastName", "Fuller", 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> </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.
|
||||
Internally, FileDb uses FilterExpressions to evaluate fields. You don't
|
||||
need to use them because you can pass in filter strings and they'll be parsed
|
||||
into FilterExpressions/FilterExpressionGroups for you. This is just to
|
||||
show you how can create them manually if you want to. 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( "(~FirstName = 'steven' OR [FirstName]
|
||||
CONTAINS 'NANC') AND LastName = 'Fuller'" );<br>
|
||||
Table table = employeesDb.SelectRecords( filterExpGrp );<br>
|
||||
<br>
|
||||
// we can manually build the same FilterExpressionGroup<br>
|
||||
var fname1Exp = FilterExpression.Parse( "~FirstName = steven" );<br>
|
||||
var fname2Exp = new FilterExpression( "FirstName", "NANCY", Equality.Contains );<br>
|
||||
var lnameExp = new FilterExpression( "LastName", "Fuller", 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> </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[] { "ID", "Firstname", "LastName",
|
||||
"Age" }, false, new string[] { "~LastName", "~FirstName", "!Age" } );</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. 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 + "Customers.fdb" );<br>
|
||||
<br>
|
||||
// select all fields and records from the database table<br>
|
||||
Table customers = customersDb.SelectAllRecords();<br>
|
||||
<br>
|
||||
Table subCusts = customers.SelectRecords( "CustomerID <> 'ALFKI'",<br>
|
||||
new string[] { "CustomerID", "CompanyName", "City" }, new string[] { "~City", "~CompanyName"
|
||||
} );</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. 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. This method creates a new database file using the Fields
|
||||
in the Table then populates it using the Records in the Table. 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[] { "ID", "Firstname", "LastName"
|
||||
} );<br>
|
||||
table.SaveToDb( "Names.fdb" );</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 "Create database from
|
||||
Table..." menu item.<br>
|
||||
</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.
|
||||
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. We use LINQ
|
||||
with FileDb to join Tables as we would using SQL.
|
||||
The difference is that instead of doing it all in a
|
||||
single step with SQL, we must do it in two steps.
|
||||
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. 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. Notice the ~
|
||||
prefix on the LastName field. This is how
|
||||
<br>
|
||||
// you can specify case insensitive searches<br>
|
||||
<br>
|
||||
Table
|
||||
employees = employeesDb.SelectRecords( "~LastName
|
||||
IN ('Fuller', 'Peacock')" );<br>
|
||||
<br>
|
||||
var query =
|
||||
from record in employees<br>
|
||||
select new<br>
|
||||
{<br>
|
||||
ID = record["EmployeeId"],<br>
|
||||
Name = record["FirstName"] + " " + record["LastName"],<br>
|
||||
Title = record["Title"]<br>
|
||||
};<br>
|
||||
<br>
|
||||
foreach( var rec in query )<br>
|
||||
{<br>
|
||||
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. Here'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<Employee></font><font face="Courier New" size="2"> employees
|
||||
= employeesDb.SelectRecords<Employee>(
|
||||
"~LastName
|
||||
IN ('Fuller', 'Peacock')" );<br>
|
||||
<br>
|
||||
var query =<br>
|
||||
from e in employees<br>
|
||||
select e;<br>
|
||||
<br>
|
||||
foreach( var emp in query )<br>
|
||||
{<br>
|
||||
Debug.WriteLine( emp.ToString() );<br>
|
||||
}</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</font>
|
||||
</blockquote>
|
||||
<p dir="ltr"><font face="Arial" size="3">Now lets tap into LINQ's real power to join tables together
|
||||
like a SQL inner join. 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. We do this to get only the records we are going to need with LINQ.
|
||||
So using FileDb with LINQ is a two step process. You first select the
|
||||
records you will need then use them in the LINQ query. If your database
|
||||
files are large, you can filter the records like this. 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( "Customers.fdb" );<br>
|
||||
ordersDb.Open( "Orders.fdb" );<br>
|
||||
orderDetailsDb.Open( "OrderDetails.fdb" );<br>
|
||||
productsDb.Open( "Products.fdb" );<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( "CustomerID IN( 'ALFKI',
|
||||
'BONAP' )" );<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>
|
||||
<br>
|
||||
filterExp =
|
||||
FilterExpression.CreateInExpressionFromTable(
|
||||
"CustomerID", customers, "CustomerID" );<br>
|
||||
FileDbNs.Table orders =
|
||||
ordersDb.SelectRecords( filterExp );<br>
|
||||
<br>
|
||||
// now get only OrderDetails records for the
|
||||
target Order records<br>
|
||||
<br>
|
||||
filterExp =
|
||||
FilterExpression.CreateInExpressionFromTable(
|
||||
"OrderID", orders, "OrderID" );<br>
|
||||
FileDbNs.Table orderDetails =
|
||||
orderDetailsDb.SelectRecords( filterExp );<br>
|
||||
<br>
|
||||
// now get only Product records for the
|
||||
target OrderDetails records<br>
|
||||
<br>
|
||||
filterExp =
|
||||
FilterExpression.CreateInExpressionFromTable(
|
||||
"ProductID", orderDetails, "ProductID" );<br>
|
||||
FileDbNs.Table products =
|
||||
productsDb.SelectRecords( filterExp );<br>
|
||||
<br>
|
||||
// now we're ready to do the join<br>
|
||||
<br>
|
||||
var query =<br>
|
||||
from custRec in customers<br>
|
||||
join orderRec in orders on custRec["CustomerID"]
|
||||
equals orderRec["CustomerID"]<br>
|
||||
join orderDetailRec in orderDetails on
|
||||
orderRec["OrderID"] equals
|
||||
orderDetailRec["OrderID"]<br>
|
||||
join productRec in products on
|
||||
orderDetailRec["ProductID"] equals
|
||||
productRec["ProductID"]<br>
|
||||
select new<br>
|
||||
{<br>
|
||||
ID = custRec["CustomerID"],<br>
|
||||
CompanyName = custRec["CompanyName"],<br>
|
||||
OrderID = orderRec["OrderID"],<br>
|
||||
OrderDate = orderRec["OrderDate"],<br>
|
||||
ProductName = productRec["ProductName"],<br>
|
||||
UnitPrice = orderDetailRec["UnitPrice"],<br>
|
||||
Quantity = orderDetailRec["Quantity"]<br>
|
||||
};<br>
|
||||
<br>
|
||||
foreach( var rec in query )<br>
|
||||
{<br>
|
||||
Debug.WriteLine( rec.ToString() );<br>
|
||||
}</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</font>
|
||||
<p><font face="Arial" size="3">Here'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( "CustomerID IN( 'ALFKI',
|
||||
'BONAP' )" );<br>
|
||||
IList<Customer> customers =
|
||||
customersDb.SelectRecords<Customer>( filterExp );<br>
|
||||
<br>
|
||||
filterExp =
|
||||
FilterExpression.CreateInExpressionFromTable<Customer>(
|
||||
"CustomerID", customers, "CustomerID" );<br>
|
||||
IList<Order> orders =
|
||||
ordersDb.SelectRecords<Order>( filterExp );<br>
|
||||
<br>
|
||||
// now get only OrderDetails records for the
|
||||
target Order records<br>
|
||||
<br>
|
||||
filterExp =
|
||||
FilterExpression.CreateInExpressionFromTable<Order>(
|
||||
"OrderID", orders, "OrderID" );<br>
|
||||
IList<OrderDetail> orderDetails =
|
||||
orderDetailsDb.SelectRecords<OrderDetail>( filterExp );<br>
|
||||
<br>
|
||||
// now get only Product records for the
|
||||
target OrderDetails records<br>
|
||||
<br>
|
||||
filterExp =
|
||||
FilterExpression.CreateInExpressionFromTable<OrderDetail>(
|
||||
"ProductID", orderDetails, "ProductID" );<br>
|
||||
IList<Product> products =
|
||||
productsDb.SelectRecords<Product>(( filterExp );<br>
|
||||
<br>
|
||||
// now we're ready to do the join<br>
|
||||
<br>
|
||||
var query =<br>
|
||||
from custRec in customers<br>
|
||||
join orderRec in orders on custRec.CustomerID
|
||||
equals orderRec.CustomerID<br>
|
||||
join orderDetailRec in orderDetails on
|
||||
orderRec.OrderID equals orderDetailRec.OrderID<br>
|
||||
join productRec in products on
|
||||
orderDetailRec.ProductID equals productRec.ProductID<br>
|
||||
select new<br>
|
||||
{<br>
|
||||
ID = custRec.CustomerID,<br>
|
||||
CompanyName = custRec.CompanyName,<br>
|
||||
OrderID = orderRec.OrderID,<br>
|
||||
OrderDate = orderRec.OrderDate,<br>
|
||||
ProductName = productRec.ProductName,<br>
|
||||
UnitPrice = orderDetailRec.UnitPrice,<br>
|
||||
Quantity = orderDetailRec.Quantity<br>
|
||||
};<br>
|
||||
<br>
|
||||
foreach( var rec in query )<br>
|
||||
{<br>
|
||||
Debug.WriteLine( rec.ToString() );<br>
|
||||
}</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</blockquote>
|
||||
<p> </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<Field>( 20 );<br>
|
||||
field = new Field( "ID", DataType.Int );<br>
|
||||
field.AutoIncStart = 0;<br>
|
||||
field.IsPrimaryKey = true;<br>
|
||||
fields.Add( field );<br>
|
||||
field = new Field( "FirstName", DataType.String );<br>
|
||||
fields.Add( field );<br>
|
||||
field = new Field( "LastName", DataType.String );<br>
|
||||
fields.Add( field );<br>
|
||||
field = new Field( "BirthDate", DataType.DateTime );<br>
|
||||
fields.Add( field );<br>
|
||||
field = new Field( "IsCitizen", DataType.Bool );<br>
|
||||
fields.Add( field );<br>
|
||||
field = new Field( "DoubleField", DataType.Double );<br>
|
||||
fields.Add( field );<br>
|
||||
field = new Field( "ByteField", DataType.Byte );<br>
|
||||
fields.Add( field );<br>
|
||||
<br>
|
||||
// array types<br>
|
||||
field = new Field( "StringArrayField", DataType.String );<br>
|
||||
field.IsArray = true;<br>
|
||||
fields.Add( field );<br>
|
||||
field = new Field( "ByteArrayField", DataType.Byte );<br>
|
||||
field.IsArray = true;<br>
|
||||
fields.Add( field );<br>
|
||||
field = new Field( "IntArrayField", DataType.Int );<br>
|
||||
field.IsArray = true;<br>
|
||||
fields.Add( field );<br>
|
||||
field = new Field( "DoubleArrayField", DataType.Double );<br>
|
||||
field.IsArray = true;<br>
|
||||
fields.Add( field );<br>
|
||||
field = new Field( "DateTimeArrayField", DataType.DateTime );<br>
|
||||
field.IsArray = true;<br>
|
||||
fields.Add( field );<br>
|
||||
field = new Field( "BoolArray", 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( "MyDatabase.fdb", 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( "FirstName", "Nancy" );<br>
|
||||
record.Add( "LastName", "Davolio" );<br>
|
||||
record.Add( "BirthDate", new DateTime( 1968, 12, 8 ) );<br>
|
||||
record.Add( "IsCitizen", true );<br>
|
||||
record.Add( "Double", 1.23 );<br>
|
||||
record.Add( "Byte", 1 );<br>
|
||||
record.Add( "StringArray", new string[] { "s1", "s2", "s3" } );<br>
|
||||
record.Add( "ByteArray", new Byte[] { 1, 2, 3, 4 } );<br>
|
||||
record.Add( "IntArray", new int[] { 100, 200, 300, 400 } );<br>
|
||||
record.Add( "DoubleArray", new double[] { 1.2, 2.4, 3.6, 4.8 } );<br>
|
||||
record.Add( "DateTimeArray", new DateTime[] { DateTime.Now, DateTime.Now,
|
||||
DateTime.Now, DateTime.Now } );<br>
|
||||
record.Add( "BoolArray", new bool[] { true, false, true, false } );<br>
|
||||
<br>
|
||||
myDb.AddRecord( record );</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</blockquote>
|
||||
<p> </p>
|
||||
</font>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -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.
|
||||
|
|
@ -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 (record != null)
|
||||
if (fileDb.IsOpen)
|
||||
{
|
||||
Record record = null;
|
||||
|
||||
try
|
||||
{
|
||||
using (var stream = new MemoryStream((byte[])record[0]))
|
||||
{
|
||||
value = formatter.Deserialize(stream);
|
||||
}
|
||||
record = fileDb.GetRecordByKey(key, new string[] { valueField }, false);
|
||||
}
|
||||
catch (Exception ex1)
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceWarning("FileDbCache: Deserializing item \"{0}\" failed: {1}", key, ex1.Message);
|
||||
Debug.WriteLine("FileDbCache: FileDb.GetRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
|
||||
}
|
||||
|
||||
if (record != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var memoryStream = new MemoryStream((byte[])record[0]))
|
||||
{
|
||||
return BitmapFrame.Create(memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
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 Dispose()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
try
|
||||
if (fileDb.IsOpen)
|
||||
{
|
||||
if (fileDb.IsOpen)
|
||||
{
|
||||
fileDb.Flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceWarning("FileDbCache: FileDb.Flush() failed: {0}", ex.Message);
|
||||
fileDb.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
try
|
||||
if (fileDb.IsOpen)
|
||||
{
|
||||
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 ex)
|
||||
{
|
||||
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
|
||||
{
|
||||
CreateDatabase();
|
||||
fileDb.UpdateRecordByKey(key, fieldValues);
|
||||
}
|
||||
catch (Exception ex2)
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceWarning("FileDbCache: Creating database {0} failed: {1}", path, ex2.Message);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)]
|
||||
74
Caching/FileDbCache.WinRT/FileDbCache.WinRT.csproj
Normal file
74
Caching/FileDbCache.WinRT/FileDbCache.WinRT.csproj
Normal 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>
|
||||
278
Caching/FileDbCache.WinRT/FileDbCache.cs
Normal file
278
Caching/FileDbCache.WinRT/FileDbCache.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Caching/FileDbCache.WinRT/Properties/AssemblyInfo.cs
Normal file
14
Caching/FileDbCache.WinRT/Properties/AssemblyInfo.cs
Normal 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)]
|
||||
Binary file not shown.
|
|
@ -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.
|
||||
|
|
@ -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" />
|
||||
|
|
@ -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);
|
||||
|
||||
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();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)]
|
||||
66
Caching/ImageFileCache.WinRT/ImageFileCache.WinRT.csproj
Normal file
66
Caching/ImageFileCache.WinRT/ImageFileCache.WinRT.csproj
Normal 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>
|
||||
94
Caching/ImageFileCache.WinRT/ImageFileCache.cs
Normal file
94
Caching/ImageFileCache.WinRT/ImageFileCache.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Caching/ImageFileCache.WinRT/Properties/AssemblyInfo.cs
Normal file
14
Caching/ImageFileCache.WinRT/Properties/AssemblyInfo.cs
Normal 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)]
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ namespace MapControl
|
|||
{
|
||||
public interface IMapElement
|
||||
{
|
||||
MapBase ParentMap { get; }
|
||||
|
||||
void SetParentMap(MapBase parentMap);
|
||||
MapBase ParentMap { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
14
MapControl/ITileImageLoader.cs
Normal file
14
MapControl/ITileImageLoader.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
22
MapControl/ImageCache.WinRT.cs
Normal file
22
MapControl/ImageCache.WinRT.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
namespace MapControl
|
||||
{
|
||||
internal struct Int32Rect
|
||||
public struct Int32Rect
|
||||
{
|
||||
public Int32Rect(int x, int y, int width, int height)
|
||||
: this()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
private void TileLayersPropertyChanged(TileLayerCollection oldTileLayers, TileLayerCollection newTileLayers)
|
||||
{
|
||||
if (oldTileLayers != null)
|
||||
{
|
||||
if (storedBackground == 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)
|
||||
{
|
||||
storedBackground = Background;
|
||||
if (tileLayer.Background != null)
|
||||
{
|
||||
Background = tileLayer.Background;
|
||||
}
|
||||
|
||||
if (tileLayer.Foreground != null)
|
||||
{
|
||||
Foreground = tileLayer.Foreground;
|
||||
}
|
||||
}
|
||||
|
||||
Background = tileLayer.Background;
|
||||
tileLayerPanel.Children.Insert(index++, tileLayer);
|
||||
}
|
||||
else if (storedBackground != null)
|
||||
}
|
||||
|
||||
private void RemoveTileLayers(int index, int count)
|
||||
{
|
||||
while (count-- > 0)
|
||||
{
|
||||
Background = storedBackground;
|
||||
storedBackground = null;
|
||||
tileLayerPanel.Children.RemoveAt(index + count);
|
||||
}
|
||||
|
||||
if (tileLayer != null && tileLayer.Foreground != null)
|
||||
if (index == 0)
|
||||
{
|
||||
if (storedForeground == null)
|
||||
{
|
||||
storedForeground = Foreground;
|
||||
}
|
||||
|
||||
Foreground = tileLayer.Foreground;
|
||||
}
|
||||
else if (storedForeground != null)
|
||||
{
|
||||
Foreground = storedForeground;
|
||||
storedForeground = null;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ namespace MapControl
|
|||
/// <summary>
|
||||
/// Relative size of the map images in relation to the current viewport size.
|
||||
/// Setting a value greater than one will let MapImageLayer request images that
|
||||
/// are larger than the viewport, in order to support smooth panning.
|
||||
/// are larger than the viewport, in order to support smooth panning.
|
||||
/// </summary>
|
||||
public double RelativeImageSize
|
||||
{
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,14 +14,17 @@ namespace MapControl
|
|||
|
||||
public static readonly DependencyProperty ParentMapProperty = ParentMapPropertyKey.DependencyProperty;
|
||||
|
||||
public MapPanel()
|
||||
{
|
||||
if (this is MapBase)
|
||||
{
|
||||
SetValue(ParentMapPropertyKey, this);
|
||||
}
|
||||
}
|
||||
|
||||
public static MapBase GetParentMap(UIElement element)
|
||||
{
|
||||
return (MapBase)element.GetValue(ParentMapProperty);
|
||||
}
|
||||
|
||||
internal void SetParentMap()
|
||||
{
|
||||
SetValue(ParentMapPropertyKey, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -20,12 +20,11 @@ namespace MapControl
|
|||
public MapBase ParentMap
|
||||
{
|
||||
get { return parentMap; }
|
||||
}
|
||||
|
||||
void IMapElement.SetParentMap(MapBase map)
|
||||
{
|
||||
parentMap = map;
|
||||
UpdateData();
|
||||
set
|
||||
{
|
||||
parentMap = value;
|
||||
UpdateData();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void UpdateData()
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ using System.Linq;
|
|||
using Windows.UI.Xaml.Media;
|
||||
#else
|
||||
using System.Windows.Media;
|
||||
|
||||
#endif
|
||||
|
||||
namespace MapControl
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:map="clr-namespace:MapControl">
|
||||
<Style TargetType="map:MapItemsControl">
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@ namespace MapControl
|
|||
}
|
||||
}
|
||||
|
||||
tile.SetImageSource(image, tileLayer.AnimateTileOpacity);
|
||||
tile.SetImage(image);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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(new PendingTile(tile));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var tile in tiles)
|
||||
{
|
||||
pendingTiles.Enqueue(tile);
|
||||
}
|
||||
var newTaskCount = Math.Min(pendingTiles.Count, maxDownloads) - taskCount;
|
||||
|
||||
while (threadCount < Math.Min(pendingTiles.Count, maxDownloads))
|
||||
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
|
||||
}
|
||||
else if (image == null) // download failed, use cached image if available
|
||||
{
|
||||
image = pendingTile.CachedImage;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (image != null || !tile.HasImageSource) // set null image if tile does not yet have an ImageSource
|
||||
if (image != null)
|
||||
{
|
||||
dispatcher.BeginInvoke(setImageAction, tile, image);
|
||||
dispatcher.BeginInvoke(new Action<Tile, ImageSource>((t, i) => t.SetImage(i)), tile, image);
|
||||
}
|
||||
else
|
||||
{
|
||||
tile.SetImage(null);
|
||||
}
|
||||
}
|
||||
|
||||
Interlocked.Decrement(ref threadCount);
|
||||
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())
|
||||
using (var responseStream = response.GetResponseStream())
|
||||
{
|
||||
var expiresHeader = response.Headers["Expires"];
|
||||
DateTime expires;
|
||||
statusCode = response.StatusCode;
|
||||
|
||||
if (expiresHeader != null &&
|
||||
DateTime.TryParse(expiresHeader, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out expires) &&
|
||||
expirationTime > expires)
|
||||
using (var responseStream = response.GetResponseStream())
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
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);
|
||||
|
||||
public static string Key(string sourceName, Tile tile)
|
||||
if (Cache == null || string.IsNullOrWhiteSpace(sourceName))
|
||||
{
|
||||
return string.Format("{0}/{1}/{2}/{3}", sourceName, tile.ZoomLevel, tile.XIndex, tile.Y);
|
||||
return null;
|
||||
}
|
||||
|
||||
public static MemoryStream ImageStream(byte[] cacheBuffer)
|
||||
return string.Format("{0}/{1}/{2}/{3}", sourceName, tile.ZoomLevel, tile.XIndex, tile.Y);
|
||||
}
|
||||
|
||||
private static bool GetCachedImage(string cacheKey, out BitmapSource image)
|
||||
{
|
||||
image = Cache.Get(cacheKey) as BitmapSource;
|
||||
|
||||
if (image == null)
|
||||
{
|
||||
return new MemoryStream(cacheBuffer, imageBufferOffset, cacheBuffer.Length - imageBufferOffset, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsExpired(byte[] cacheBuffer)
|
||||
{
|
||||
return DateTime.FromBinary(BitConverter.ToInt64(cacheBuffer, 0)) < DateTime.UtcNow;
|
||||
}
|
||||
var metadata = (BitmapMetadata)image.Metadata;
|
||||
DateTime expiration;
|
||||
|
||||
public static byte[] CreateBuffer(Stream imageStream, int length, DateTime expirationTime)
|
||||
// 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;
|
||||
}
|
||||
|
||||
private static void SetCachedImage(string cacheKey, BitmapSource image, DateTime expiration)
|
||||
{
|
||||
var bitmap = BitmapFrame.Create(image);
|
||||
var metadata = (BitmapMetadata)bitmap.Metadata;
|
||||
|
||||
// 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))
|
||||
{
|
||||
using (var memoryStream = length > 0 ? new MemoryStream(length + imageBufferOffset) : new MemoryStream())
|
||||
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)
|
||||
{
|
||||
memoryStream.Write(BitConverter.GetBytes(expirationTime.ToBinary()), 0, imageBufferOffset);
|
||||
imageStream.CopyTo(memoryStream);
|
||||
|
||||
return length > 0 ? memoryStream.GetBuffer() : memoryStream.ToArray();
|
||||
expiration = maxExpiration;
|
||||
}
|
||||
}
|
||||
|
||||
return expiration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
{
|
||||
image = new BitmapImage(uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
var bitmap = new BitmapImage();
|
||||
image = bitmap;
|
||||
|
||||
Task.Run(async () => await LoadCachedImage(tileLayer, tile, uri, bitmap));
|
||||
}
|
||||
tile.SetImage(null);
|
||||
}
|
||||
else if (!useCache)
|
||||
{
|
||||
tile.SetImage(new BitmapImage(uri));
|
||||
}
|
||||
else
|
||||
{
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
DownloadAndCacheImage(uri, bitmap, cacheKey);
|
||||
}
|
||||
}
|
||||
var tile = pendingTile.Tile;
|
||||
var image = pendingTile.Image;
|
||||
var uri = pendingTile.Uri;
|
||||
var extension = Path.GetExtension(uri.LocalPath);
|
||||
|
||||
private async Task LoadImageFromBuffer(IBuffer buffer, BitmapImage bitmap)
|
||||
{
|
||||
using (var stream = new InMemoryRandomAccessStream())
|
||||
{
|
||||
await stream.WriteAsync(buffer);
|
||||
await stream.FlushAsync();
|
||||
stream.Seek(0);
|
||||
|
||||
await bitmap.Dispatcher.RunAsync(CoreDispatcherPriority.Low, async () =>
|
||||
if (string.IsNullOrEmpty(extension) || extension == ".jpeg")
|
||||
{
|
||||
try
|
||||
{
|
||||
await bitmap.SetSourceAsync(stream);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex.Message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
extension = ".jpg";
|
||||
}
|
||||
|
||||
httpClient.GetAsync(uri).Completed = async (request, status) =>
|
||||
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)
|
||||
{
|
||||
if (status == AsyncStatus.Completed)
|
||||
await LoadImageFromBuffer(tile, image, cachedBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
var statusCode = await DownloadImage(tile, image, uri, cacheKey, cachedBuffer);
|
||||
|
||||
if (statusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
using (var response = request.GetResults())
|
||||
{
|
||||
await LoadImageFromHttpResponse(response, bitmap, cacheKey);
|
||||
}
|
||||
tileSource.IgnoreTile(tile.XIndex, tile.Y, tile.ZoomLevel); // do not request again
|
||||
}
|
||||
else
|
||||
}
|
||||
}
|
||||
|
||||
Interlocked.Decrement(ref taskCount);
|
||||
}
|
||||
|
||||
private async Task<HttpStatusCode> DownloadImage(Tile tile, BitmapSource image, Uri uri, string cacheKey, IBuffer cachedBuffer)
|
||||
{
|
||||
HttpStatusCode result = HttpStatusCode.None;
|
||||
|
||||
try
|
||||
{
|
||||
using (var httpClient = new HttpClient(new HttpBaseProtocolFilter { AllowAutoRedirect = false }))
|
||||
using (var response = await httpClient.GetAsync(uri))
|
||||
{
|
||||
result = response.StatusCode;
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
Debug.WriteLine("{0}: {1}", uri, request.ErrorCode != null ? request.ErrorCode.Message : status.ToString());
|
||||
await LoadImageFromHttpResponse(response, tile, image, cacheKey);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
Debug.WriteLine("{0}: ({1}) {2}", uri, (int)response.StatusCode, response.ReasonPhrase);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine("{0}: {1}", uri, ex.Message);
|
||||
}
|
||||
|
||||
if (cachedBuffer != null)
|
||||
{
|
||||
await LoadImageFromBuffer(tile, image, cachedBuffer);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task LoadImageFromHttpResponse(HttpResponseMessage response, BitmapImage bitmap, string cacheKey)
|
||||
private async Task LoadImageFromHttpResponse(HttpResponseMessage response, Tile tile, BitmapSource image, string cacheKey)
|
||||
{
|
||||
if (response.IsSuccessStatusCode)
|
||||
using (var stream = new InMemoryRandomAccessStream())
|
||||
{
|
||||
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 () =>
|
||||
var loaded = await LoadImageFromStream(tile, image, stream);
|
||||
|
||||
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);
|
||||
|
||||
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 bitmap.SetSourceAsync(stream);
|
||||
await image.SetSourceAsync(stream);
|
||||
tile.SetImage(image, true, false);
|
||||
|
||||
// cache image asynchronously, after successful decoding
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
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);
|
||||
});
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
MapControl/TileLayer.WPF.cs
Normal file
17
MapControl/TileLayer.WPF.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
@ -42,17 +37,40 @@ namespace MapControl
|
|||
return new TileLayer
|
||||
{
|
||||
SourceName = "OpenStreetMap",
|
||||
Description="© [OpenStreetMap Contributors](http://www.openstreetmap.org/copyright)",
|
||||
Description = "© [OpenStreetMap Contributors](http://www.openstreetmap.org/copyright)",
|
||||
TileSource = new TileSource { UriFormat = "http://{c}.tile.openstreetmap.org/{z}/{x}/{y}.png" }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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,123 +80,190 @@ 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();
|
||||
UpdateTiles();
|
||||
parentMap.TileGridChanged -= UpdateTiles;
|
||||
ClearValue(RenderTransformProperty);
|
||||
}
|
||||
|
||||
parentMap = value;
|
||||
|
||||
if (parentMap != null)
|
||||
{
|
||||
parentMap.TileGridChanged += UpdateTiles;
|
||||
RenderTransform = parentMap.TileLayerTransform;
|
||||
}
|
||||
|
||||
UpdateTiles();
|
||||
}
|
||||
}
|
||||
|
||||
internal void ClearTiles()
|
||||
protected virtual void UpdateTiles()
|
||||
{
|
||||
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)
|
||||
if (tiles.Count > 0)
|
||||
{
|
||||
tileImageLoader.CancelLoadTiles(this);
|
||||
}
|
||||
|
||||
SelectTiles();
|
||||
Children.Clear();
|
||||
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>();
|
||||
|
||||
for (var z = minZoomLevel; z <= maxZoomLevel; z++)
|
||||
if (parentMap != null && TileSource != null)
|
||||
{
|
||||
var tileSize = 1 << (zoomLevel - z);
|
||||
var x1 = (int)Math.Floor((double)grid.X / tileSize); // may be negative
|
||||
var x2 = (grid.X + grid.Width - 1) / tileSize;
|
||||
var y1 = Math.Max(grid.Y / tileSize, 0);
|
||||
var y2 = Math.Min((grid.Y + grid.Height - 1) / tileSize, (1 << z) - 1);
|
||||
var grid = parentMap.TileGrid;
|
||||
var zoomLevel = parentMap.TileZoomLevel;
|
||||
var maxZoomLevel = Math.Min(zoomLevel, MaxZoomLevel);
|
||||
var minZoomLevel = MinZoomLevel;
|
||||
|
||||
for (var y = y1; y <= y2; y++)
|
||||
if (parentMap.TileLayers.FirstOrDefault() != this)
|
||||
{
|
||||
for (var x = x1; x <= x2; x++)
|
||||
// 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);
|
||||
var x1 = (int)Math.Floor((double)grid.X / tileSize); // may be negative
|
||||
var x2 = (grid.X + grid.Width - 1) / tileSize;
|
||||
var y1 = Math.Max(grid.Y / tileSize, 0);
|
||||
var y2 = Math.Min((grid.Y + grid.Height - 1) / tileSize, (1 << z) - 1);
|
||||
|
||||
for (var y = y1; y <= y2; y++)
|
||||
{
|
||||
var tile = tiles.FirstOrDefault(t => t.ZoomLevel == z && t.X == x && t.Y == y);
|
||||
|
||||
if (tile == null)
|
||||
for (var x = x1; x <= x2; x++)
|
||||
{
|
||||
tile = new Tile(z, x, y);
|
||||
var tile = tiles.FirstOrDefault(t => t.ZoomLevel == z && t.X == x && t.Y == y);
|
||||
|
||||
var equivalentTile = tiles.FirstOrDefault(
|
||||
t => t.Image.Source != null && t.ZoomLevel == z && t.XIndex == tile.XIndex && t.Y == y);
|
||||
|
||||
if (equivalentTile != null)
|
||||
if (tile == null)
|
||||
{
|
||||
// do not animate to avoid flicker when crossing date line
|
||||
tile.SetImageSource(equivalentTile.Image.Source, false);
|
||||
}
|
||||
}
|
||||
tile = new Tile(z, x, y);
|
||||
|
||||
newTiles.Add(tile);
|
||||
var equivalentTile = tiles.FirstOrDefault(
|
||||
t => t.Image.Source != null && t.ZoomLevel == z && t.XIndex == tile.XIndex && t.Y == y);
|
||||
|
||||
if (equivalentTile != null)
|
||||
{
|
||||
// do not animate to avoid flicker when crossing 180°
|
||||
tile.SetImage(equivalentTile.Image.Source, false);
|
||||
}
|
||||
}
|
||||
|
||||
newTiles.Add(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -188,12 +273,18 @@ namespace MapControl
|
|||
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
foreach (var tile in tiles)
|
||||
if (parentMap != null)
|
||||
{
|
||||
var tileSize = (double)(256 << (zoomLevel - tile.ZoomLevel));
|
||||
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));
|
||||
foreach (var tile in tiles)
|
||||
{
|
||||
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(x, y, tileSize, tileSize));
|
||||
}
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
@ -31,7 +34,7 @@ namespace MapControl
|
|||
{
|
||||
this.uriFormat = uriFormat;
|
||||
}
|
||||
|
||||
|
||||
public string UriFormat
|
||||
{
|
||||
get { return uriFormat; }
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -15,8 +15,7 @@ namespace PhoneApplication
|
|||
|
||||
public MainPage()
|
||||
{
|
||||
//TileImageLoader.Cache = new ImageFileCache();
|
||||
//BingMapsTileLayer.ApiKey = ...
|
||||
//BingMapsTileLayer.ApiKey = "...";
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="SilverlightApplication.App">
|
||||
<Application.Resources>
|
||||
</Application.Resources>
|
||||
|
|
|
|||
|
|
@ -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/>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ namespace SilverlightApplication
|
|||
|
||||
public MainPage()
|
||||
{
|
||||
//BingMapsTileLayer.ApiKey = ...
|
||||
//BingMapsTileLayer.ApiKey = "...";
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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/>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
@ -105,7 +89,7 @@ namespace WpfApplication
|
|||
{
|
||||
var selectedItem = (ComboBoxItem)tileLayerComboBox.SelectedItem;
|
||||
|
||||
map.TileLayer = tileLayers[(string)selectedItem.Tag];
|
||||
map.TileLayer = tileLayers[(string)selectedItem.Tag];
|
||||
|
||||
mapLegend.Inlines.Clear();
|
||||
mapLegend.Inlines.AddRange(map.TileLayer.DescriptionInlines);
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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"]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue