Index: /applications/viewer/jmapviewer/.settings/org.eclipse.jdt.core.prefs
===================================================================
--- /applications/viewer/jmapviewer/.settings/org.eclipse.jdt.core.prefs	(revision 11783)
+++ /applications/viewer/jmapviewer/.settings/org.eclipse.jdt.core.prefs	(revision 11783)
@@ -0,0 +1,258 @@
+#Tue Oct 21 16:34:49 CEST 2008
+eclipse.preferences.version=1
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=0
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=0
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=false
+org.eclipse.jdt.core.formatter.comment.format_header=false
+org.eclipse.jdt.core.formatter.comment.format_html=false
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=false
+org.eclipse.jdt.core.formatter.comment.format_line_comments=false
+org.eclipse.jdt.core.formatter.comment.format_source_code=false
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=false
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=do not insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
+org.eclipse.jdt.core.formatter.comment.line_length=100
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=120
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
+org.eclipse.jdt.core.formatter.tabulation.char=space
+org.eclipse.jdt.core.formatter.tabulation.size=4
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
Index: /applications/viewer/jmapviewer/.settings/org.eclipse.jdt.ui.prefs
===================================================================
--- /applications/viewer/jmapviewer/.settings/org.eclipse.jdt.ui.prefs	(revision 11783)
+++ /applications/viewer/jmapviewer/.settings/org.eclipse.jdt.ui.prefs	(revision 11783)
@@ -0,0 +1,4 @@
+#Tue Oct 21 16:34:49 CEST 2008
+eclipse.preferences.version=1
+formatter_profile=_JOSM
+formatter_settings_version=11
Index: /applications/viewer/jmapviewer/build.xml
===================================================================
--- /applications/viewer/jmapviewer/build.xml	(revision 11782)
+++ /applications/viewer/jmapviewer/build.xml	(revision 11783)
@@ -13,5 +13,8 @@
 
 	<target name="build">
-		<javac srcdir="src" destdir="bin" target="1.5" debug="true"/>
+		<javac srcdir="src" destdir="bin" target="1.5" debug="true">
+			<include name="org/openstreetmap/gui/jmapviewer/**"/>
+		</javac>
+
 		<copy todir="bin">
 			<fileset dir="src">
@@ -25,5 +28,5 @@
 		<delete file="JMapViewer_src.jar" />
 		<jar destfile="JMapViewer.jar" filesetmanifest="mergewithoutmain">
-			<fileset dir="bin" includes="**/jmapviewer/**" >
+			<fileset dir="bin" includes="**/jmapviewer/**">
 			</fileset>
 			<fileset dir="src" includes="**/jmapviewer/**" />
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java	(revision 11782)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java	(revision 11783)
@@ -11,9 +11,7 @@
 import java.awt.event.ActionListener;
 import java.awt.geom.Point2D;
-import java.awt.image.BufferedImage;
 import java.util.LinkedList;
 import java.util.List;
 
-import javax.imageio.ImageIO;
 import javax.swing.ImageIcon;
 import javax.swing.JButton;
@@ -40,534 +38,522 @@
 public class JMapViewer extends JPanel implements TileLoaderListener {
 
-	private static final long serialVersionUID = 1L;
-
-	/**
-	 * Vectors for clock-wise tile painting
-	 */
-	protected static final Point[] move = { new Point(1, 0), new Point(0, 1),
-			new Point(-1, 0), new Point(0, -1) };
-
-	public static final int MAX_ZOOM = 22;
-	public static final int MIN_ZOOM = 0;
-
-	protected TileLoader tileLoader;
-	protected TileCache tileCache;
-	protected TileSource tileSource;
-
-	protected List<MapMarker> mapMarkerList;
-	protected boolean mapMarkersVisible;
-	protected boolean tileGridVisible;
-
-	/**
-	 * x- and y-position of the center of this map-panel on the world map
-	 * denoted in screen pixel regarding the current zoom level.
-	 */
-	protected Point center;
-
-	/**
-	 * Current zoom level
-	 */
-	protected int zoom;
-
-	protected JSlider zoomSlider;
-	protected JButton zoomInButton;
-	protected JButton zoomOutButton;
-
-	/**
-	 * Hourglass image that is displayed until a map tile has been loaded
-	 */
-	protected BufferedImage loadingImage;
-
-	JobDispatcher jobDispatcher;
-
-	/**
-	 * Creates a standard {@link JMapViewer} instance that can be controlled via
-	 * mouse: hold right mouse button for moving, double click left mouse button
-	 * or use mouse wheel for zooming. Loaded tiles are stored the
-	 * {@link MemoryTileCache} and the tile loader uses 4 parallel threads for
-	 * retrieving the tiles.
-	 */
-	public JMapViewer() {
-		this(new MemoryTileCache(), 4);
-		new DefaultMapController(this);
-	}
-
-	public JMapViewer(TileCache tileCache, int downloadThreadCount) {
-		super();
-		tileSource = new OsmTileSource.Mapnik();
-		tileLoader = new OsmTileLoader(this);
-		this.tileCache = tileCache;
-		jobDispatcher = JobDispatcher.getInstance();
-		mapMarkerList = new LinkedList<MapMarker>();
-		mapMarkersVisible = true;
-		tileGridVisible = false;
-		setLayout(null);
-		initializeZoomSlider();
-		setMinimumSize(new Dimension(Tile.SIZE, Tile.SIZE));
-		setPreferredSize(new Dimension(400, 400));
-		try {
-			loadingImage = ImageIO.read(JMapViewer.class
-					.getResourceAsStream("images/hourglass.png"));
-		} catch (Exception e1) {
-			loadingImage = null;
-		}
-		setDisplayPositionByLatLon(50, 9, 3);
-	}
-
-	protected void initializeZoomSlider() {
-		zoomSlider = new JSlider(MIN_ZOOM, tileSource.getMaxZoom());
-		zoomSlider.setOrientation(JSlider.VERTICAL);
-		zoomSlider.setBounds(10, 10, 30, 150);
-		zoomSlider.setOpaque(false);
-		zoomSlider.addChangeListener(new ChangeListener() {
-			public void stateChanged(ChangeEvent e) {
-				setZoom(zoomSlider.getValue());
-			}
-		});
-		add(zoomSlider);
-		int size = 18;
-		try {
-			ImageIcon icon = new ImageIcon(getClass().getResource(
-					"images/plus.png"));
-			zoomInButton = new JButton(icon);
-		} catch (Exception e) {
-			zoomInButton = new JButton("+");
-			zoomInButton.setFont(new Font("sansserif", Font.BOLD, 9));
-			zoomInButton.setMargin(new Insets(0, 0, 0, 0));
-		}
-		zoomInButton.setBounds(4, 155, size, size);
-		zoomInButton.addActionListener(new ActionListener() {
-
-			public void actionPerformed(ActionEvent e) {
-				zoomIn();
-			}
-		});
-		add(zoomInButton);
-		try {
-			ImageIcon icon = new ImageIcon(getClass().getResource(
-					"images/minus.png"));
-			zoomOutButton = new JButton(icon);
-		} catch (Exception e) {
-			zoomOutButton = new JButton("-");
-			zoomOutButton.setFont(new Font("sansserif", Font.BOLD, 9));
-			zoomOutButton.setMargin(new Insets(0, 0, 0, 0));
-		}
-		zoomOutButton.setBounds(8 + size, 155, size, size);
-		zoomOutButton.addActionListener(new ActionListener() {
-
-			public void actionPerformed(ActionEvent e) {
-				zoomOut();
-			}
-		});
-		add(zoomOutButton);
-	}
-
-	/**
-	 * Changes the map pane so that it is centered on the specified coordinate
-	 * at the given zoom level.
-	 * 
-	 * @param lat
-	 *            latitude of the specified coordinate
-	 * @param lon
-	 *            longitude of the specified coordinate
-	 * @param zoom
-	 *            {@link #MIN_ZOOM} <= zoom level <= {@link #MAX_ZOOM}
-	 */
-	public void setDisplayPositionByLatLon(double lat, double lon, int zoom) {
-		setDisplayPositionByLatLon(new Point(getWidth() / 2, getHeight() / 2),
-				lat, lon, zoom);
-	}
-
-	/**
-	 * Changes the map pane so that the specified coordinate at the given zoom
-	 * level is displayed on the map at the screen coordinate
-	 * <code>mapPoint</code>.
-	 * 
-	 * @param mapPoint
-	 *            point on the map denoted in pixels where the coordinate should
-	 *            be set
-	 * @param lat
-	 *            latitude of the specified coordinate
-	 * @param lon
-	 *            longitude of the specified coordinate
-	 * @param zoom
-	 *            {@link #MIN_ZOOM} <= zoom level <=
-	 *            {@link TileSource#getMaxZoom()}
-	 */
-	public void setDisplayPositionByLatLon(Point mapPoint, double lat,
-			double lon, int zoom) {
-		int x = OsmMercator.LonToX(lon, zoom);
-		int y = OsmMercator.LatToY(lat, zoom);
-		setDisplayPosition(mapPoint, x, y, zoom);
-	}
-
-	public void setDisplayPosition(int x, int y, int zoom) {
-		setDisplayPosition(new Point(getWidth() / 2, getHeight() / 2), x, y,
-				zoom);
-	}
-
-	public void setDisplayPosition(Point mapPoint, int x, int y, int zoom) {
-		if (zoom > tileSource.getMaxZoom() || zoom < MIN_ZOOM)
-			return;
-
-		// Get the plain tile number
-		Point p = new Point();
-		p.x = x - mapPoint.x + getWidth() / 2;
-		p.y = y - mapPoint.y + getHeight() / 2;
-		center = p;
-		setIgnoreRepaint(true);
-		try {
-			int oldZoom = this.zoom;
-			this.zoom = zoom;
-			if (oldZoom != zoom)
-				zoomChanged(oldZoom);
-			if (zoomSlider.getValue() != zoom)
-				zoomSlider.setValue(zoom);
-		} finally {
-			setIgnoreRepaint(false);
-			repaint();
-		}
-	}
-
-	/**
-	 * Sets the displayed map pane and zoom level so that all map markers are
-	 * visible.
-	 */
-	public void setDisplayToFitMapMarkers() {
-		if (mapMarkerList == null || mapMarkerList.size() == 0)
-			return;
-		int x_min = Integer.MAX_VALUE;
-		int y_min = Integer.MAX_VALUE;
-		int x_max = Integer.MIN_VALUE;
-		int y_max = Integer.MIN_VALUE;
-		int mapZoomMax = tileSource.getMaxZoom();
-		for (MapMarker marker : mapMarkerList) {
-			int x = OsmMercator.LonToX(marker.getLon(), mapZoomMax);
-			int y = OsmMercator.LatToY(marker.getLat(), mapZoomMax);
-			x_max = Math.max(x_max, x);
-			y_max = Math.max(y_max, y);
-			x_min = Math.min(x_min, x);
-			y_min = Math.min(y_min, y);
-		}
-		int height = Math.max(0, getHeight());
-		int width = Math.max(0, getWidth());
-		// System.out.println(x_min + " < x < " + x_max);
-		// System.out.println(y_min + " < y < " + y_max);
-		// System.out.println("tiles: " + width + " " + height);
-		int newZoom = mapZoomMax;
-		int x = x_max - x_min;
-		int y = y_max - y_min;
-		while (x > width || y > height) {
-			// System.out.println("zoom: " + zoom + " -> " + x + " " + y);
-			newZoom--;
-			x >>= 1;
-			y >>= 1;
-		}
-		x = x_min + (x_max - x_min) / 2;
-		y = y_min + (y_max - y_min) / 2;
-		int z = 1 << (mapZoomMax - newZoom);
-		x /= z;
-		y /= z;
-		setDisplayPosition(x, y, newZoom);
-	}
-
-	public Point2D.Double getPosition() {
-		double lon = OsmMercator.XToLon(center.x, zoom);
-		double lat = OsmMercator.YToLat(center.y, zoom);
-		return new Point2D.Double(lat, lon);
-	}
-
-	public Point2D.Double getPosition(Point mapPoint) {
-		int x = center.x + mapPoint.x - getWidth() / 2;
-		int y = center.y + mapPoint.y - getHeight() / 2;
-		double lon = OsmMercator.XToLon(x, zoom);
-		double lat = OsmMercator.YToLat(y, zoom);
-		return new Point2D.Double(lat, lon);
-	}
-
-	/**
-	 * Calculates the position on the map of a given coordinate
-	 * 
-	 * @param lat
-	 * @param lon
-	 * @return point on the map or <code>null</code> if the point is not visible
-	 */
-	public Point getMapPosition(double lat, double lon) {
-		int x = OsmMercator.LonToX(lon, zoom);
-		int y = OsmMercator.LatToY(lat, zoom);
-		x -= center.x - getWidth() / 2;
-		y -= center.y - getHeight() / 2;
-		if (x < 0 || y < 0 || x > getWidth() || y > getHeight())
-			return null;
-		return new Point(x, y);
-	}
-
-	@Override
-	protected void paintComponent(Graphics g) {
-		super.paintComponent(g);
-
-		int iMove = 0;
-
-		int tilex = center.x / Tile.SIZE;
-		int tiley = center.y / Tile.SIZE;
-		int off_x = (center.x % Tile.SIZE);
-		int off_y = (center.y % Tile.SIZE);
-
-		int w2 = getWidth() / 2;
-		int h2 = getHeight() / 2;
-		int posx = w2 - off_x;
-		int posy = h2 - off_y;
-
-		int diff_left = off_x;
-		int diff_right = Tile.SIZE - off_x;
-		int diff_top = off_y;
-		int diff_bottom = Tile.SIZE - off_y;
-
-		boolean start_left = diff_left < diff_right;
-		boolean start_top = diff_top < diff_bottom;
-
-		if (start_top) {
-			if (start_left)
-				iMove = 2;
-			else
-				iMove = 3;
-		} else {
-			if (start_left)
-				iMove = 1;
-			else
-				iMove = 0;
-		} // calculate the visibility borders
-		int x_min = -Tile.SIZE;
-		int y_min = -Tile.SIZE;
-		int x_max = getWidth();
-		int y_max = getHeight();
-
-		// paint the tiles in a spiral, starting from center of the map
-		boolean painted = true;
-		int x = 0;
-		while (painted) {
-			painted = false;
-			for (int i = 0; i < 4; i++) {
-				if (i % 2 == 0)
-					x++;
-				for (int j = 0; j < x; j++) {
-					if (x_min <= posx && posx <= x_max && y_min <= posy
-							&& posy <= y_max) {
-						// tile is visible
-						Tile tile = getTile(tilex, tiley, zoom);
-						if (tile != null) {
-							painted = true;
-							tile.paint(g, posx, posy);
-							if (tileGridVisible)
-								g.drawRect(posx, posy, Tile.SIZE, Tile.SIZE);
-						}
-					}
-					Point p = move[iMove];
-					posx += p.x * Tile.SIZE;
-					posy += p.y * Tile.SIZE;
-					tilex += p.x;
-					tiley += p.y;
-				}
-				iMove = (iMove + 1) % move.length;
-			}
-		}
-		// outer border of the map
-		int mapSize = Tile.SIZE << zoom;
-		g.drawRect(w2 - center.x, h2 - center.y, mapSize, mapSize);
-
-		// g.drawString("Tiles in cache: " + tileCache.getTileCount(), 50, 20);
-		if (!mapMarkersVisible || mapMarkerList == null)
-			return;
-		for (MapMarker marker : mapMarkerList) {
-			Point p = getMapPosition(marker.getLat(), marker.getLon());
-			// System.out.println(marker + " -> " + p);
-			if (p != null)
-				marker.paint(g, p);
-		}
-	}
-
-	/**
-	 * Moves the visible map pane.
-	 * 
-	 * @param x
-	 *            horizontal movement in pixel.
-	 * @param y
-	 *            vertical movement in pixel
-	 */
-	public void moveMap(int x, int y) {
-		center.x += x;
-		center.y += y;
-		repaint();
-	}
-
-	/**
-	 * @return the current zoom level
-	 */
-	public int getZoom() {
-		return zoom;
-	}
-
-	/**
-	 * Increases the current zoom level by one
-	 */
-	public void zoomIn() {
-		setZoom(zoom + 1);
-	}
-
-	/**
-	 * Increases the current zoom level by one
-	 */
-	public void zoomIn(Point mapPoint) {
-		setZoom(zoom + 1, mapPoint);
-	}
-
-	/**
-	 * Decreases the current zoom level by one
-	 */
-	public void zoomOut() {
-		setZoom(zoom - 1);
-	}
-
-	/**
-	 * Decreases the current zoom level by one
-	 */
-	public void zoomOut(Point mapPoint) {
-		setZoom(zoom - 1, mapPoint);
-	}
-
-	public void setZoom(int zoom, Point mapPoint) {
-		if (zoom > tileSource.getMaxZoom() || zoom == this.zoom)
-			return;
-		Point2D.Double zoomPos = getPosition(mapPoint);
-		jobDispatcher.cancelOutstandingJobs(); // Clearing outstanding load
-		// requests
-		setDisplayPositionByLatLon(mapPoint, zoomPos.x, zoomPos.y, zoom);
-	}
-
-	public void setZoom(int zoom) {
-		setZoom(zoom, new Point(getWidth() / 2, getHeight() / 2));
-	}
-
-	/**
-	 * retrieves a tile from the cache. If the tile is not present in the cache
-	 * a load job is added to the working queue of {@link JobThread}.
-	 * 
-	 * @param tilex
-	 * @param tiley
-	 * @param zoom
-	 * @return specified tile from the cache or <code>null</code> if the tile
-	 *         was not found in the cache.
-	 */
-	protected Tile getTile(int tilex, int tiley, int zoom) {
-		int max = (1 << zoom);
-		if (tilex < 0 || tilex >= max || tiley < 0 || tiley >= max)
-			return null;
-		Tile tile = tileCache.getTile(tileSource, tilex, tiley, zoom);
-		if (tile == null) {
-			tile = new Tile(tileSource, tilex, tiley, zoom, loadingImage);
-			tileCache.addTile(tile);
-			tile.loadPlaceholderFromCache(tileCache);
-		}
-		if (!tile.isLoaded()) {
-			jobDispatcher.addJob(tileLoader.createTileLoaderJob(tileSource,
-					tilex, tiley, zoom));
-		}
-		return tile;
-	}
-
-	/**
-	 * Every time the zoom level changes this method is called. Override it in
-	 * derived implementations for adapting zoom dependent values. The new zoom
-	 * level can be obtained via {@link #getZoom()}.
-	 * 
-	 * @param oldZoom
-	 *            the previous zoom level
-	 */
-	protected void zoomChanged(int oldZoom) {
-		zoomSlider.setToolTipText("Zoom level " + zoom);
-		zoomInButton.setToolTipText("Zoom to level " + (zoom + 1));
-		zoomOutButton.setToolTipText("Zoom to level " + (zoom - 1));
-		zoomOutButton.setEnabled(zoom > 0);
-		zoomInButton.setEnabled(zoom < tileSource.getMaxZoom());
-	}
-
-	public boolean isTileGridVisible() {
-		return tileGridVisible;
-	}
-
-	public void setTileGridVisible(boolean tileGridVisible) {
-		this.tileGridVisible = tileGridVisible;
-		repaint();
-	}
-
-	public boolean getMapMarkersVisible() {
-		return mapMarkersVisible;
-	}
-
-	/**
-	 * Enables or disables painting of the {@link MapMarker}
-	 * 
-	 * @param mapMarkersVisible
-	 * @see #addMapMarker(MapMarker)
-	 * @see #getMapMarkerList()
-	 */
-	public void setMapMarkerVisible(boolean mapMarkersVisible) {
-		this.mapMarkersVisible = mapMarkersVisible;
-		repaint();
-	}
-
-	public void setMapMarkerList(List<MapMarker> mapMarkerList) {
-		this.mapMarkerList = mapMarkerList;
-		repaint();
-	}
-
-	public List<MapMarker> getMapMarkerList() {
-		return mapMarkerList;
-	}
-
-	public void addMapMarker(MapMarker marker) {
-		mapMarkerList.add(marker);
-	}
-
-	public void setZoomContolsVisible(boolean visible) {
-		zoomSlider.setVisible(visible);
-		zoomInButton.setVisible(visible);
-		zoomOutButton.setVisible(visible);
-	}
-
-	public boolean getZoomContolsVisible() {
-		return zoomSlider.isVisible();
-	}
-
-	public TileCache getTileCache() {
-		return tileCache;
-	}
-
-	public TileLoader getTileLoader() {
-		return tileLoader;
-	}
-
-	public void setTileLoader(TileLoader tileLoader) {
-		this.tileLoader = tileLoader;
-	}
-
-	public TileSource getTileLayerSource() {
-		return tileSource;
-	}
-
-	public void setTileSource(TileSource tileSource) {
-		if (tileSource.getMaxZoom() > MAX_ZOOM)
-			throw new RuntimeException("Zoom level too high");
-		this.tileSource = tileSource;
-		zoomSlider.setMaximum(tileSource.getMaxZoom());
-		jobDispatcher.cancelOutstandingJobs();
-		if (zoom > tileSource.getMaxZoom())
-			setZoom(tileSource.getMaxZoom());
-		repaint();
-	}
-
-	public void tileLoadingFinished(Tile tile) {
-		repaint();
-	}
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Vectors for clock-wise tile painting
+     */
+    protected static final Point[] move = { new Point(1, 0), new Point(0, 1), new Point(-1, 0), new Point(0, -1) };
+
+    public static final int MAX_ZOOM = 22;
+    public static final int MIN_ZOOM = 0;
+
+    protected TileLoader tileLoader;
+    protected TileCache tileCache;
+    protected TileSource tileSource;
+
+    protected List<MapMarker> mapMarkerList;
+    protected boolean mapMarkersVisible;
+    protected boolean tileGridVisible;
+
+    /**
+     * x- and y-position of the center of this map-panel on the world map
+     * denoted in screen pixel regarding the current zoom level.
+     */
+    protected Point center;
+
+    /**
+     * Current zoom level
+     */
+    protected int zoom;
+
+    protected JSlider zoomSlider;
+    protected JButton zoomInButton;
+    protected JButton zoomOutButton;
+
+    JobDispatcher jobDispatcher;
+
+    /**
+     * Creates a standard {@link JMapViewer} instance that can be controlled via
+     * mouse: hold right mouse button for moving, double click left mouse button
+     * or use mouse wheel for zooming. Loaded tiles are stored the
+     * {@link MemoryTileCache} and the tile loader uses 4 parallel threads for
+     * retrieving the tiles.
+     */
+    public JMapViewer() {
+        this(new MemoryTileCache(), 4);
+        new DefaultMapController(this);
+    }
+
+    public JMapViewer(TileCache tileCache, int downloadThreadCount) {
+        super();
+        tileSource = new OsmTileSource.Mapnik();
+        tileLoader = new OsmTileLoader(this);
+        this.tileCache = tileCache;
+        jobDispatcher = JobDispatcher.getInstance();
+        mapMarkerList = new LinkedList<MapMarker>();
+        mapMarkersVisible = true;
+        tileGridVisible = false;
+        setLayout(null);
+        initializeZoomSlider();
+        setMinimumSize(new Dimension(Tile.SIZE, Tile.SIZE));
+        setPreferredSize(new Dimension(400, 400));
+        setDisplayPositionByLatLon(50, 9, 3);
+    }
+
+    protected void initializeZoomSlider() {
+        zoomSlider = new JSlider(MIN_ZOOM, tileSource.getMaxZoom());
+        zoomSlider.setOrientation(JSlider.VERTICAL);
+        zoomSlider.setBounds(10, 10, 30, 150);
+        zoomSlider.setOpaque(false);
+        zoomSlider.addChangeListener(new ChangeListener() {
+            public void stateChanged(ChangeEvent e) {
+                setZoom(zoomSlider.getValue());
+            }
+        });
+        add(zoomSlider);
+        int size = 18;
+        try {
+            ImageIcon icon = new ImageIcon(getClass().getResource("images/plus.png"));
+            zoomInButton = new JButton(icon);
+        } catch (Exception e) {
+            zoomInButton = new JButton("+");
+            zoomInButton.setFont(new Font("sansserif", Font.BOLD, 9));
+            zoomInButton.setMargin(new Insets(0, 0, 0, 0));
+        }
+        zoomInButton.setBounds(4, 155, size, size);
+        zoomInButton.addActionListener(new ActionListener() {
+
+            public void actionPerformed(ActionEvent e) {
+                zoomIn();
+            }
+        });
+        add(zoomInButton);
+        try {
+            ImageIcon icon = new ImageIcon(getClass().getResource("images/minus.png"));
+            zoomOutButton = new JButton(icon);
+        } catch (Exception e) {
+            zoomOutButton = new JButton("-");
+            zoomOutButton.setFont(new Font("sansserif", Font.BOLD, 9));
+            zoomOutButton.setMargin(new Insets(0, 0, 0, 0));
+        }
+        zoomOutButton.setBounds(8 + size, 155, size, size);
+        zoomOutButton.addActionListener(new ActionListener() {
+
+            public void actionPerformed(ActionEvent e) {
+                zoomOut();
+            }
+        });
+        add(zoomOutButton);
+    }
+
+    /**
+     * Changes the map pane so that it is centered on the specified coordinate
+     * at the given zoom level.
+     * 
+     * @param lat
+     *            latitude of the specified coordinate
+     * @param lon
+     *            longitude of the specified coordinate
+     * @param zoom
+     *            {@link #MIN_ZOOM} <= zoom level <= {@link #MAX_ZOOM}
+     */
+    public void setDisplayPositionByLatLon(double lat, double lon, int zoom) {
+        setDisplayPositionByLatLon(new Point(getWidth() / 2, getHeight() / 2), lat, lon, zoom);
+    }
+
+    /**
+     * Changes the map pane so that the specified coordinate at the given zoom
+     * level is displayed on the map at the screen coordinate
+     * <code>mapPoint</code>.
+     * 
+     * @param mapPoint
+     *            point on the map denoted in pixels where the coordinate should
+     *            be set
+     * @param lat
+     *            latitude of the specified coordinate
+     * @param lon
+     *            longitude of the specified coordinate
+     * @param zoom
+     *            {@link #MIN_ZOOM} <= zoom level <=
+     *            {@link TileSource#getMaxZoom()}
+     */
+    public void setDisplayPositionByLatLon(Point mapPoint, double lat, double lon, int zoom) {
+        int x = OsmMercator.LonToX(lon, zoom);
+        int y = OsmMercator.LatToY(lat, zoom);
+        setDisplayPosition(mapPoint, x, y, zoom);
+    }
+
+    public void setDisplayPosition(int x, int y, int zoom) {
+        setDisplayPosition(new Point(getWidth() / 2, getHeight() / 2), x, y, zoom);
+    }
+
+    public void setDisplayPosition(Point mapPoint, int x, int y, int zoom) {
+        if (zoom > tileSource.getMaxZoom() || zoom < MIN_ZOOM)
+            return;
+
+        // Get the plain tile number
+        Point p = new Point();
+        p.x = x - mapPoint.x + getWidth() / 2;
+        p.y = y - mapPoint.y + getHeight() / 2;
+        center = p;
+        setIgnoreRepaint(true);
+        try {
+            int oldZoom = this.zoom;
+            this.zoom = zoom;
+            if (oldZoom != zoom)
+                zoomChanged(oldZoom);
+            if (zoomSlider.getValue() != zoom)
+                zoomSlider.setValue(zoom);
+        } finally {
+            setIgnoreRepaint(false);
+            repaint();
+        }
+    }
+
+    /**
+     * Sets the displayed map pane and zoom level so that all map markers are
+     * visible.
+     */
+    public void setDisplayToFitMapMarkers() {
+        if (mapMarkerList == null || mapMarkerList.size() == 0)
+            return;
+        int x_min = Integer.MAX_VALUE;
+        int y_min = Integer.MAX_VALUE;
+        int x_max = Integer.MIN_VALUE;
+        int y_max = Integer.MIN_VALUE;
+        int mapZoomMax = tileSource.getMaxZoom();
+        for (MapMarker marker : mapMarkerList) {
+            int x = OsmMercator.LonToX(marker.getLon(), mapZoomMax);
+            int y = OsmMercator.LatToY(marker.getLat(), mapZoomMax);
+            x_max = Math.max(x_max, x);
+            y_max = Math.max(y_max, y);
+            x_min = Math.min(x_min, x);
+            y_min = Math.min(y_min, y);
+        }
+        int height = Math.max(0, getHeight());
+        int width = Math.max(0, getWidth());
+        // System.out.println(x_min + " < x < " + x_max);
+        // System.out.println(y_min + " < y < " + y_max);
+        // System.out.println("tiles: " + width + " " + height);
+        int newZoom = mapZoomMax;
+        int x = x_max - x_min;
+        int y = y_max - y_min;
+        while (x > width || y > height) {
+            // System.out.println("zoom: " + zoom + " -> " + x + " " + y);
+            newZoom--;
+            x >>= 1;
+            y >>= 1;
+        }
+        x = x_min + (x_max - x_min) / 2;
+        y = y_min + (y_max - y_min) / 2;
+        int z = 1 << (mapZoomMax - newZoom);
+        x /= z;
+        y /= z;
+        setDisplayPosition(x, y, newZoom);
+    }
+
+    public Point2D.Double getPosition() {
+        double lon = OsmMercator.XToLon(center.x, zoom);
+        double lat = OsmMercator.YToLat(center.y, zoom);
+        return new Point2D.Double(lat, lon);
+    }
+
+    public Point2D.Double getPosition(Point mapPoint) {
+        int x = center.x + mapPoint.x - getWidth() / 2;
+        int y = center.y + mapPoint.y - getHeight() / 2;
+        double lon = OsmMercator.XToLon(x, zoom);
+        double lat = OsmMercator.YToLat(y, zoom);
+        return new Point2D.Double(lat, lon);
+    }
+
+    /**
+     * Calculates the position on the map of a given coordinate
+     * 
+     * @param lat
+     * @param lon
+     * @return point on the map or <code>null</code> if the point is not visible
+     */
+    public Point getMapPosition(double lat, double lon) {
+        int x = OsmMercator.LonToX(lon, zoom);
+        int y = OsmMercator.LatToY(lat, zoom);
+        x -= center.x - getWidth() / 2;
+        y -= center.y - getHeight() / 2;
+        if (x < 0 || y < 0 || x > getWidth() || y > getHeight())
+            return null;
+        return new Point(x, y);
+    }
+
+    @Override
+    protected void paintComponent(Graphics g) {
+        super.paintComponent(g);
+
+        int iMove = 0;
+
+        int tilex = center.x / Tile.SIZE;
+        int tiley = center.y / Tile.SIZE;
+        int off_x = (center.x % Tile.SIZE);
+        int off_y = (center.y % Tile.SIZE);
+
+        int w2 = getWidth() / 2;
+        int h2 = getHeight() / 2;
+        int posx = w2 - off_x;
+        int posy = h2 - off_y;
+
+        int diff_left = off_x;
+        int diff_right = Tile.SIZE - off_x;
+        int diff_top = off_y;
+        int diff_bottom = Tile.SIZE - off_y;
+
+        boolean start_left = diff_left < diff_right;
+        boolean start_top = diff_top < diff_bottom;
+
+        if (start_top) {
+            if (start_left)
+                iMove = 2;
+            else
+                iMove = 3;
+        } else {
+            if (start_left)
+                iMove = 1;
+            else
+                iMove = 0;
+        } // calculate the visibility borders
+        int x_min = -Tile.SIZE;
+        int y_min = -Tile.SIZE;
+        int x_max = getWidth();
+        int y_max = getHeight();
+
+        // paint the tiles in a spiral, starting from center of the map
+        boolean painted = true;
+        int x = 0;
+        while (painted) {
+            painted = false;
+            for (int i = 0; i < 4; i++) {
+                if (i % 2 == 0)
+                    x++;
+                for (int j = 0; j < x; j++) {
+                    if (x_min <= posx && posx <= x_max && y_min <= posy && posy <= y_max) {
+                        // tile is visible
+                        Tile tile = getTile(tilex, tiley, zoom);
+                        if (tile != null) {
+                            painted = true;
+                            tile.paint(g, posx, posy);
+                            if (tileGridVisible)
+                                g.drawRect(posx, posy, Tile.SIZE, Tile.SIZE);
+                        }
+                    }
+                    Point p = move[iMove];
+                    posx += p.x * Tile.SIZE;
+                    posy += p.y * Tile.SIZE;
+                    tilex += p.x;
+                    tiley += p.y;
+                }
+                iMove = (iMove + 1) % move.length;
+            }
+        }
+        // outer border of the map
+        int mapSize = Tile.SIZE << zoom;
+        g.drawRect(w2 - center.x, h2 - center.y, mapSize, mapSize);
+
+        // g.drawString("Tiles in cache: " + tileCache.getTileCount(), 50, 20);
+        if (!mapMarkersVisible || mapMarkerList == null)
+            return;
+        for (MapMarker marker : mapMarkerList) {
+            Point p = getMapPosition(marker.getLat(), marker.getLon());
+            // System.out.println(marker + " -> " + p);
+            if (p != null)
+                marker.paint(g, p);
+        }
+    }
+
+    /**
+     * Moves the visible map pane.
+     * 
+     * @param x
+     *            horizontal movement in pixel.
+     * @param y
+     *            vertical movement in pixel
+     */
+    public void moveMap(int x, int y) {
+        center.x += x;
+        center.y += y;
+        repaint();
+    }
+
+    /**
+     * @return the current zoom level
+     */
+    public int getZoom() {
+        return zoom;
+    }
+
+    /**
+     * Increases the current zoom level by one
+     */
+    public void zoomIn() {
+        setZoom(zoom + 1);
+    }
+
+    /**
+     * Increases the current zoom level by one
+     */
+    public void zoomIn(Point mapPoint) {
+        setZoom(zoom + 1, mapPoint);
+    }
+
+    /**
+     * Decreases the current zoom level by one
+     */
+    public void zoomOut() {
+        setZoom(zoom - 1);
+    }
+
+    /**
+     * Decreases the current zoom level by one
+     */
+    public void zoomOut(Point mapPoint) {
+        setZoom(zoom - 1, mapPoint);
+    }
+
+    public void setZoom(int zoom, Point mapPoint) {
+        if (zoom > tileSource.getMaxZoom() || zoom < tileSource.getMinZoom() || zoom == this.zoom)
+            return;
+        Point2D.Double zoomPos = getPosition(mapPoint);
+        jobDispatcher.cancelOutstandingJobs(); // Clearing outstanding load
+        // requests
+        setDisplayPositionByLatLon(mapPoint, zoomPos.x, zoomPos.y, zoom);
+    }
+
+    public void setZoom(int zoom) {
+        setZoom(zoom, new Point(getWidth() / 2, getHeight() / 2));
+    }
+
+    /**
+     * retrieves a tile from the cache. If the tile is not present in the cache
+     * a load job is added to the working queue of {@link JobThread}.
+     * 
+     * @param tilex
+     * @param tiley
+     * @param zoom
+     * @return specified tile from the cache or <code>null</code> if the tile
+     *         was not found in the cache.
+     */
+    protected Tile getTile(int tilex, int tiley, int zoom) {
+        int max = (1 << zoom);
+        if (tilex < 0 || tilex >= max || tiley < 0 || tiley >= max)
+            return null;
+        Tile tile = tileCache.getTile(tileSource, tilex, tiley, zoom);
+        if (tile == null) {
+            tile = new Tile(tileSource, tilex, tiley, zoom);
+            tileCache.addTile(tile);
+            tile.loadPlaceholderFromCache(tileCache);
+        }
+        if (!tile.isLoaded()) {
+            jobDispatcher.addJob(tileLoader.createTileLoaderJob(tileSource, tilex, tiley, zoom));
+        }
+        return tile;
+    }
+
+    /**
+     * Every time the zoom level changes this method is called. Override it in
+     * derived implementations for adapting zoom dependent values. The new zoom
+     * level can be obtained via {@link #getZoom()}.
+     * 
+     * @param oldZoom
+     *            the previous zoom level
+     */
+    protected void zoomChanged(int oldZoom) {
+        zoomSlider.setToolTipText("Zoom level " + zoom);
+        zoomInButton.setToolTipText("Zoom to level " + (zoom + 1));
+        zoomOutButton.setToolTipText("Zoom to level " + (zoom - 1));
+        zoomOutButton.setEnabled(zoom > tileSource.getMinZoom());
+        zoomInButton.setEnabled(zoom < tileSource.getMaxZoom());
+    }
+
+    public boolean isTileGridVisible() {
+        return tileGridVisible;
+    }
+
+    public void setTileGridVisible(boolean tileGridVisible) {
+        this.tileGridVisible = tileGridVisible;
+        repaint();
+    }
+
+    public boolean getMapMarkersVisible() {
+        return mapMarkersVisible;
+    }
+
+    /**
+     * Enables or disables painting of the {@link MapMarker}
+     * 
+     * @param mapMarkersVisible
+     * @see #addMapMarker(MapMarker)
+     * @see #getMapMarkerList()
+     */
+    public void setMapMarkerVisible(boolean mapMarkersVisible) {
+        this.mapMarkersVisible = mapMarkersVisible;
+        repaint();
+    }
+
+    public void setMapMarkerList(List<MapMarker> mapMarkerList) {
+        this.mapMarkerList = mapMarkerList;
+        repaint();
+    }
+
+    public List<MapMarker> getMapMarkerList() {
+        return mapMarkerList;
+    }
+
+    public void addMapMarker(MapMarker marker) {
+        mapMarkerList.add(marker);
+    }
+
+    public void setZoomContolsVisible(boolean visible) {
+        zoomSlider.setVisible(visible);
+        zoomInButton.setVisible(visible);
+        zoomOutButton.setVisible(visible);
+    }
+
+    public boolean getZoomContolsVisible() {
+        return zoomSlider.isVisible();
+    }
+
+    public TileCache getTileCache() {
+        return tileCache;
+    }
+
+    public TileLoader getTileLoader() {
+        return tileLoader;
+    }
+
+    public void setTileLoader(TileLoader tileLoader) {
+        this.tileLoader = tileLoader;
+    }
+
+    public TileSource getTileLayerSource() {
+        return tileSource;
+    }
+
+    public TileSource getTileSource() {
+        return tileSource;
+    }
+
+    public void setTileSource(TileSource tileSource) {
+        if (tileSource.getMaxZoom() > MAX_ZOOM)
+            throw new RuntimeException("Maximum zoom level too high");
+        if (tileSource.getMinZoom() < MIN_ZOOM)
+            throw new RuntimeException("Minumim zoom level too low");
+        this.tileSource = tileSource;
+        zoomSlider.setMinimum(tileSource.getMinZoom());
+        zoomSlider.setMaximum(tileSource.getMaxZoom());
+        jobDispatcher.cancelOutstandingJobs();
+        if (zoom > tileSource.getMaxZoom())
+            setZoom(tileSource.getMaxZoom());
+        repaint();
+    }
+
+    public void tileLoadingFinished(Tile tile, boolean success) {
+        repaint();
+    }
 
 }
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmFileCacheTileLoader.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmFileCacheTileLoader.java	(revision 11782)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmFileCacheTileLoader.java	(revision 11783)
@@ -14,4 +14,5 @@
 import java.net.URLConnection;
 import java.nio.charset.Charset;
+import java.util.logging.Logger;
 
 import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
@@ -30,338 +31,324 @@
 public class OsmFileCacheTileLoader extends OsmTileLoader {
 
-	private static final String ETAG_FILE_EXT = ".etag";
-
-	private static final Charset ETAG_CHARSET = Charset.forName("UTF-8");
-
-	public static final long FILE_AGE_ONE_DAY = 1000 * 60 * 60 * 24;
-	public static final long FILE_AGE_ONE_WEEK = FILE_AGE_ONE_DAY * 7;
-
-	protected String cacheDirBase;
-
-	protected long maxCacheFileAge = FILE_AGE_ONE_WEEK;
-	protected long recheckAfter = FILE_AGE_ONE_DAY;
-
-	public OsmFileCacheTileLoader(TileLoaderListener map) {
-		super(map);
-		String tempDir = System.getProperty("java.io.tmpdir");
-		try {
-			if (tempDir == null)
-				throw new IOException();
-			File cacheDir = new File(tempDir, "JMapViewerTiles");
-			// System.out.println(cacheDir);
-			if (!cacheDir.exists() && !cacheDir.mkdirs())
-				throw new IOException();
-			cacheDirBase = cacheDir.getAbsolutePath();
-		} catch (Exception e) {
-			cacheDirBase = "tiles";
-		}
-	}
-
-	public Runnable createTileLoaderJob(final TileSource source,
-			final int tilex, final int tiley, final int zoom) {
-		return new FileLoadJob(source, tilex, tiley, zoom);
-	}
-
-	protected class FileLoadJob implements Runnable {
-		InputStream input = null;
-
-		int tilex, tiley, zoom;
-		Tile tile;
-		TileSource source;
-		File tileCacheDir;
-		File tileFile = null;
-		long fileAge = 0;
-		boolean fileTilePainted = false;
-
-		public FileLoadJob(TileSource source, int tilex, int tiley, int zoom) {
-			super();
-			this.source = source;
-			this.tilex = tilex;
-			this.tiley = tiley;
-			this.zoom = zoom;
-		}
-
-		public void run() {
-			TileCache cache = listener.getTileCache();
-			synchronized (cache) {
-				tile = cache.getTile(source, tilex, tiley, zoom);
-				if (tile == null || tile.isLoaded() || tile.loading)
-					return;
-				tile.loading = true;
-			}
-			tileCacheDir = new File(cacheDirBase, source.getName());
-			if (!tileCacheDir.exists())
-				tileCacheDir.mkdirs();
-			if (loadTileFromFile())
-				return;
-			if (fileTilePainted) {
-				Runnable job = new Runnable() {
-
-					public void run() {
-						loadorUpdateTile();
-					}
-				};
-				JobDispatcher.getInstance().addJob(job);
-			} else {
-				loadorUpdateTile();
-			}
-		}
-
-		protected void loadorUpdateTile() {
-
-			try {
-				// System.out.println("Loading tile from OSM: " + tile);
-				HttpURLConnection urlConn = loadTileFromOsm(tile);
-				if (tileFile != null) {
-					switch (source.getTileUpdate()) {
-					case IfModifiedSince:
-						urlConn.setIfModifiedSince(fileAge);
-						break;
-					case LastModified:
-						if (!isOsmTileNewer(fileAge)) {
-							// System.out.println(
-							// "LastModified: Local version is up to date: " +
-							// tile);
-							tile.setLoaded(true);
-							tileFile.setLastModified(System.currentTimeMillis()
-									- maxCacheFileAge + recheckAfter);
-							return;
-						}
-						break;
-					}
-				}
-				if (source.getTileUpdate() == TileUpdate.ETag
-						|| source.getTileUpdate() == TileUpdate.IfNoneMatch) {
-					if (tileFile != null) {
-						String fileETag = loadETagfromFile();
-						if (fileETag != null) {
-							switch (source.getTileUpdate()) {
-							case IfNoneMatch:
-								urlConn.addRequestProperty("If-None-Match",
-										fileETag);
-								break;
-							case ETag:
-								if (hasOsmTileETag(fileETag)) {
-									tile.setLoaded(true);
-									tileFile.setLastModified(System
-											.currentTimeMillis()
-											- maxCacheFileAge + recheckAfter);
-									return;
-								}
-							}
-						}
-					}
-
-					String eTag = urlConn.getHeaderField("ETag");
-					saveETagToFile(eTag);
-				}
-				if (urlConn.getResponseCode() == 304) {
-					// If we are isModifiedSince or If-None-Match has been set
-					// and the server answers with a HTTP 304 = "Not Modified"
-					System.out.println("Local version is up to date: " + tile);
-					tile.setLoaded(true);
-					tileFile.setLastModified(System.currentTimeMillis()
-							- maxCacheFileAge + recheckAfter);
-					return;
-				}
-
-				byte[] buffer = loadTileInBuffer(urlConn);
-				if (buffer != null) {
-					tile.loadImage(new ByteArrayInputStream(buffer));
-					tile.setLoaded(true);
-					listener.tileLoadingFinished(tile);
-					saveTileToFile(buffer);
-				} else {
-					tile.setLoaded(true);
-				}
-			} catch (Exception e) {
-				if (input == null /* || !input.isStopped() */)
-					System.err.println("failed loading " + zoom + "/" + tilex
-							+ "/" + tiley + " " + e.getMessage());
-			} finally {
-				tile.loading = false;
-			}
-		}
-
-		protected boolean loadTileFromFile() {
-			FileInputStream fin = null;
-			try {
-				tileFile = getTileFile();
-				fin = new FileInputStream(tileFile);
-				if (fin.available() == 0)
-					throw new IOException("File empty");
-				tile.loadImage(fin);
-				fin.close();
-				fileAge = tileFile.lastModified();
-				boolean oldTile = System.currentTimeMillis() - fileAge > maxCacheFileAge;
-				// System.out.println("Loaded from file: " + tile);
-				if (!oldTile) {
-					tile.setLoaded(true);
-					listener.tileLoadingFinished(tile);
-					fileTilePainted = true;
-					return true;
-				}
-				listener.tileLoadingFinished(tile);
-				fileTilePainted = true;
-			} catch (Exception e) {
-				try {
-					if (fin != null) {
-						fin.close();
-						tileFile.delete();
-					}
-				} catch (Exception e1) {
-				}
-				tileFile = null;
-				fileAge = 0;
-			}
-			return false;
-		}
-
-		protected byte[] loadTileInBuffer(URLConnection urlConn)
-				throws IOException {
-			input = urlConn.getInputStream();
-			ByteArrayOutputStream bout = new ByteArrayOutputStream(input
-					.available());
-			byte[] buffer = new byte[2048];
-			boolean finished = false;
-			do {
-				int read = input.read(buffer);
-				if (read >= 0)
-					bout.write(buffer, 0, read);
-				else
-					finished = true;
-			} while (!finished);
-			if (bout.size() == 0)
-				return null;
-			return bout.toByteArray();
-		}
-
-		/**
-		 * Performs a <code>HEAD</code> request for retrieving the
-		 * <code>LastModified</code> header value.
-		 * 
-		 * Note: This does only work with servers providing the
-		 * <code>LastModified</code> header:
-		 * <ul>
-		 * <li>{@link OsmTileLoader#MAP_OSMA} - supported</li>
-		 * <li>{@link OsmTileLoader#MAP_MAPNIK} - not supported</li>
-		 * </ul>
-		 * 
-		 * @param fileAge
-		 * @return <code>true</code> if the tile on the server is newer than the
-		 *         file
-		 * @throws IOException
-		 */
-		protected boolean isOsmTileNewer(long fileAge) throws IOException {
-			URL url;
-			url = new URL(tile.getUrl());
-			HttpURLConnection urlConn = (HttpURLConnection) url
-					.openConnection();
-			urlConn.setRequestMethod("HEAD");
-			urlConn.setReadTimeout(30000); // 30 seconds read timeout
-			// System.out.println("Tile age: " + new
-			// Date(urlConn.getLastModified()) + " / "
-			// + new Date(fileAge));
-			long lastModified = urlConn.getLastModified();
-			if (lastModified == 0)
-				return true; // no LastModified time returned
-			return (lastModified > fileAge);
-		}
-
-		protected boolean hasOsmTileETag(String eTag) throws IOException {
-			URL url;
-			url = new URL(tile.getUrl());
-			HttpURLConnection urlConn = (HttpURLConnection) url
-					.openConnection();
-			urlConn.setRequestMethod("HEAD");
-			urlConn.setReadTimeout(30000); // 30 seconds read timeout
-			// System.out.println("Tile age: " + new
-			// Date(urlConn.getLastModified()) + " / "
-			// + new Date(fileAge));
-			String osmETag = urlConn.getHeaderField("ETag");
-			if (osmETag == null)
-				return true;
-			return (osmETag.equals(eTag));
-		}
-
-		protected File getTileFile() throws IOException {
-			return new File(tileCacheDir + "/" + tile.getZoom() + "_"
-					+ tile.getXtile() + "_" + tile.getYtile() + "."
-					+ source.getTileType());
-		}
-
-		protected void saveTileToFile(byte[] rawData) {
-			try {
-				FileOutputStream f = new FileOutputStream(tileCacheDir + "/"
-						+ tile.getZoom() + "_" + tile.getXtile() + "_"
-						+ tile.getYtile() + "." + source.getTileType());
-				f.write(rawData);
-				f.close();
-				// System.out.println("Saved tile to file: " + tile);
-			} catch (Exception e) {
-				System.err.println("Failed to save tile content: "
-						+ e.getLocalizedMessage());
-			}
-		}
-
-		protected void saveETagToFile(String eTag) {
-			try {
-				FileOutputStream f = new FileOutputStream(tileCacheDir + "/"
-						+ tile.getZoom() + "_" + tile.getXtile() + "_"
-						+ tile.getYtile() + ETAG_FILE_EXT);
-				f.write(eTag.getBytes(ETAG_CHARSET.name()));
-				f.close();
-			} catch (Exception e) {
-				System.err.println("Failed to save ETag: "
-						+ e.getLocalizedMessage());
-			}
-		}
-
-		protected String loadETagfromFile() {
-			try {
-				FileInputStream f = new FileInputStream(tileCacheDir + "/"
-						+ tile.getZoom() + "_" + tile.getXtile() + "_"
-						+ tile.getYtile() + ETAG_FILE_EXT);
-				byte[] buf = new byte[f.available()];
-				f.read(buf);
-				f.close();
-				return new String(buf, ETAG_CHARSET.name());
-			} catch (Exception e) {
-				return null;
-			}
-		}
-
-	}
-
-	public long getMaxFileAge() {
-		return maxCacheFileAge;
-	}
-
-	/**
-	 * Sets the maximum age of the local cached tile in the file system. If a
-	 * local tile is older than the specified file age
-	 * {@link OsmFileCacheTileLoader} will connect to the tile server and check
-	 * if a newer tile is available using the mechanism specified for the
-	 * selected tile source/server.
-	 * 
-	 * @param maxFileAge
-	 *            maximum age in milliseconds
-	 * @see #FILE_AGE_ONE_DAY
-	 * @see #FILE_AGE_ONE_WEEK
-	 * @see TileSource#getTileUpdate()
-	 */
-	public void setCacheMaxFileAge(long maxFileAge) {
-		this.maxCacheFileAge = maxFileAge;
-	}
-
-	public String getCacheDirBase() {
-		return cacheDirBase;
-	}
-
-	public void setTileCacheDir(String tileCacheDir) {
-		File dir = new File(tileCacheDir);
-		dir.mkdirs();
-		this.cacheDirBase = dir.getAbsolutePath();
-	}
+    private static final Logger log = Logger.getLogger(OsmFileCacheTileLoader.class.getName());
+
+    private static final String ETAG_FILE_EXT = ".etag";
+
+    private static final Charset ETAG_CHARSET = Charset.forName("UTF-8");
+
+    public static final long FILE_AGE_ONE_DAY = 1000 * 60 * 60 * 24;
+    public static final long FILE_AGE_ONE_WEEK = FILE_AGE_ONE_DAY * 7;
+
+    protected String cacheDirBase;
+
+    protected long maxCacheFileAge = FILE_AGE_ONE_WEEK;
+    protected long recheckAfter = FILE_AGE_ONE_DAY;
+
+    public OsmFileCacheTileLoader(TileLoaderListener map) {
+        super(map);
+        String tempDir = System.getProperty("java.io.tmpdir");
+        try {
+            if (tempDir == null)
+                throw new IOException();
+            File cacheDir = new File(tempDir, "JMapViewerTiles");
+            log.finest("Tile cache directory: " + cacheDir);
+            if (!cacheDir.exists() && !cacheDir.mkdirs())
+                throw new IOException();
+            cacheDirBase = cacheDir.getAbsolutePath();
+        } catch (Exception e) {
+            cacheDirBase = "tiles";
+        }
+    }
+
+    public Runnable createTileLoaderJob(final TileSource source, final int tilex, final int tiley, final int zoom) {
+        return new FileLoadJob(source, tilex, tiley, zoom);
+    }
+
+    protected class FileLoadJob implements Runnable {
+        InputStream input = null;
+
+        int tilex, tiley, zoom;
+        Tile tile;
+        TileSource source;
+        File tileCacheDir;
+        File tileFile = null;
+        long fileAge = 0;
+        boolean fileTilePainted = false;
+
+        public FileLoadJob(TileSource source, int tilex, int tiley, int zoom) {
+            super();
+            this.source = source;
+            this.tilex = tilex;
+            this.tiley = tiley;
+            this.zoom = zoom;
+        }
+
+        public void run() {
+            TileCache cache = listener.getTileCache();
+            synchronized (cache) {
+                tile = cache.getTile(source, tilex, tiley, zoom);
+                if (tile == null || tile.isLoaded() || tile.loading)
+                    return;
+                tile.loading = true;
+            }
+            tileCacheDir = new File(cacheDirBase, source.getName());
+            if (!tileCacheDir.exists())
+                tileCacheDir.mkdirs();
+            if (loadTileFromFile())
+                return;
+            if (fileTilePainted) {
+                Runnable job = new Runnable() {
+
+                    public void run() {
+                        loadOrUpdateTile();
+                    }
+                };
+                JobDispatcher.getInstance().addJob(job);
+            } else {
+                loadOrUpdateTile();
+            }
+        }
+
+        protected void loadOrUpdateTile() {
+
+            try {
+                // log.finest("Loading tile from OSM: " + tile);
+                HttpURLConnection urlConn = loadTileFromOsm(tile);
+                if (tileFile != null) {
+                    switch (source.getTileUpdate()) {
+                    case IfModifiedSince:
+                        urlConn.setIfModifiedSince(fileAge);
+                        break;
+                    case LastModified:
+                        if (!isOsmTileNewer(fileAge)) {
+                            log.finest("LastModified test: local version is up to date: " + tile);
+                            tile.setLoaded(true);
+                            tileFile.setLastModified(System.currentTimeMillis() - maxCacheFileAge + recheckAfter);
+                            return;
+                        }
+                        break;
+                    }
+                }
+                if (source.getTileUpdate() == TileUpdate.ETag || source.getTileUpdate() == TileUpdate.IfNoneMatch) {
+                    if (tileFile != null) {
+                        String fileETag = loadETagfromFile();
+                        if (fileETag != null) {
+                            switch (source.getTileUpdate()) {
+                            case IfNoneMatch:
+                                urlConn.addRequestProperty("If-None-Match", fileETag);
+                                break;
+                            case ETag:
+                                if (hasOsmTileETag(fileETag)) {
+                                    tile.setLoaded(true);
+                                    tileFile.setLastModified(System.currentTimeMillis() - maxCacheFileAge
+                                            + recheckAfter);
+                                    return;
+                                }
+                            }
+                        }
+                    }
+
+                    String eTag = urlConn.getHeaderField("ETag");
+                    saveETagToFile(eTag);
+                }
+                if (urlConn.getResponseCode() == 304) {
+                    // If we are isModifiedSince or If-None-Match has been set
+                    // and the server answers with a HTTP 304 = "Not Modified"
+                    log.finest("ETag test: local version is up to date: " + tile);
+                    tile.setLoaded(true);
+                    tileFile.setLastModified(System.currentTimeMillis() - maxCacheFileAge + recheckAfter);
+                    return;
+                }
+
+                byte[] buffer = loadTileInBuffer(urlConn);
+                if (buffer != null) {
+                    tile.loadImage(new ByteArrayInputStream(buffer));
+                    tile.setLoaded(true);
+                    listener.tileLoadingFinished(tile, true);
+                    saveTileToFile(buffer);
+                } else {
+                    tile.setLoaded(true);
+                }
+            } catch (Exception e) {
+                tile.setImage(Tile.ERROR_IMAGE);
+                listener.tileLoadingFinished(tile, false);
+                if (input == null)
+                    System.err.println("failed loading " + zoom + "/" + tilex + "/" + tiley + " " + e.getMessage());
+            } finally {
+                tile.loading = false;
+                tile.setLoaded(true);
+            }
+        }
+
+        protected boolean loadTileFromFile() {
+            FileInputStream fin = null;
+            try {
+                tileFile = getTileFile();
+                fin = new FileInputStream(tileFile);
+                if (fin.available() == 0)
+                    throw new IOException("File empty");
+                tile.loadImage(fin);
+                fin.close();
+                fileAge = tileFile.lastModified();
+                boolean oldTile = System.currentTimeMillis() - fileAge > maxCacheFileAge;
+                // System.out.println("Loaded from file: " + tile);
+                if (!oldTile) {
+                    tile.setLoaded(true);
+                    listener.tileLoadingFinished(tile, true);
+                    fileTilePainted = true;
+                    return true;
+                }
+                listener.tileLoadingFinished(tile, true);
+                fileTilePainted = true;
+            } catch (Exception e) {
+                try {
+                    if (fin != null) {
+                        fin.close();
+                        tileFile.delete();
+                    }
+                } catch (Exception e1) {
+                }
+                tileFile = null;
+                fileAge = 0;
+            }
+            return false;
+        }
+
+        protected byte[] loadTileInBuffer(URLConnection urlConn) throws IOException {
+            input = urlConn.getInputStream();
+            ByteArrayOutputStream bout = new ByteArrayOutputStream(input.available());
+            byte[] buffer = new byte[2048];
+            boolean finished = false;
+            do {
+                int read = input.read(buffer);
+                if (read >= 0)
+                    bout.write(buffer, 0, read);
+                else
+                    finished = true;
+            } while (!finished);
+            if (bout.size() == 0)
+                return null;
+            return bout.toByteArray();
+        }
+
+        /**
+         * Performs a <code>HEAD</code> request for retrieving the
+         * <code>LastModified</code> header value.
+         * 
+         * Note: This does only work with servers providing the
+         * <code>LastModified</code> header:
+         * <ul>
+         * <li>{@link OsmTileLoader#MAP_OSMA} - supported</li>
+         * <li>{@link OsmTileLoader#MAP_MAPNIK} - not supported</li>
+         * </ul>
+         * 
+         * @param fileAge
+         * @return <code>true</code> if the tile on the server is newer than the
+         *         file
+         * @throws IOException
+         */
+        protected boolean isOsmTileNewer(long fileAge) throws IOException {
+            URL url;
+            url = new URL(tile.getUrl());
+            HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
+            urlConn.setRequestMethod("HEAD");
+            urlConn.setReadTimeout(30000); // 30 seconds read timeout
+            // System.out.println("Tile age: " + new
+            // Date(urlConn.getLastModified()) + " / "
+            // + new Date(fileAge));
+            long lastModified = urlConn.getLastModified();
+            if (lastModified == 0)
+                return true; // no LastModified time returned
+            return (lastModified > fileAge);
+        }
+
+        protected boolean hasOsmTileETag(String eTag) throws IOException {
+            URL url;
+            url = new URL(tile.getUrl());
+            HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
+            urlConn.setRequestMethod("HEAD");
+            urlConn.setReadTimeout(30000); // 30 seconds read timeout
+            // System.out.println("Tile age: " + new
+            // Date(urlConn.getLastModified()) + " / "
+            // + new Date(fileAge));
+            String osmETag = urlConn.getHeaderField("ETag");
+            if (osmETag == null)
+                return true;
+            return (osmETag.equals(eTag));
+        }
+
+        protected File getTileFile() throws IOException {
+            return new File(tileCacheDir + "/" + tile.getZoom() + "_" + tile.getXtile() + "_" + tile.getYtile() + "."
+                    + source.getTileType());
+        }
+
+        protected void saveTileToFile(byte[] rawData) {
+            try {
+                FileOutputStream f = new FileOutputStream(tileCacheDir + "/" + tile.getZoom() + "_" + tile.getXtile()
+                        + "_" + tile.getYtile() + "." + source.getTileType());
+                f.write(rawData);
+                f.close();
+                // System.out.println("Saved tile to file: " + tile);
+            } catch (Exception e) {
+                System.err.println("Failed to save tile content: " + e.getLocalizedMessage());
+            }
+        }
+
+        protected void saveETagToFile(String eTag) {
+            try {
+                FileOutputStream f = new FileOutputStream(tileCacheDir + "/" + tile.getZoom() + "_" + tile.getXtile()
+                        + "_" + tile.getYtile() + ETAG_FILE_EXT);
+                f.write(eTag.getBytes(ETAG_CHARSET.name()));
+                f.close();
+            } catch (Exception e) {
+                System.err.println("Failed to save ETag: " + e.getLocalizedMessage());
+            }
+        }
+
+        protected String loadETagfromFile() {
+            try {
+                FileInputStream f = new FileInputStream(tileCacheDir + "/" + tile.getZoom() + "_" + tile.getXtile()
+                        + "_" + tile.getYtile() + ETAG_FILE_EXT);
+                byte[] buf = new byte[f.available()];
+                f.read(buf);
+                f.close();
+                return new String(buf, ETAG_CHARSET.name());
+            } catch (Exception e) {
+                return null;
+            }
+        }
+
+    }
+
+    public long getMaxFileAge() {
+        return maxCacheFileAge;
+    }
+
+    /**
+     * Sets the maximum age of the local cached tile in the file system. If a
+     * local tile is older than the specified file age
+     * {@link OsmFileCacheTileLoader} will connect to the tile server and check
+     * if a newer tile is available using the mechanism specified for the
+     * selected tile source/server.
+     * 
+     * @param maxFileAge
+     *            maximum age in milliseconds
+     * @see #FILE_AGE_ONE_DAY
+     * @see #FILE_AGE_ONE_WEEK
+     * @see TileSource#getTileUpdate()
+     */
+    public void setCacheMaxFileAge(long maxFileAge) {
+        this.maxCacheFileAge = maxFileAge;
+    }
+
+    public String getCacheDirBase() {
+        return cacheDirBase;
+    }
+
+    public void setTileCacheDir(String tileCacheDir) {
+        File dir = new File(tileCacheDir);
+        dir.mkdirs();
+        this.cacheDirBase = dir.getAbsolutePath();
+    }
 
 }
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileLoader.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileLoader.java	(revision 11782)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileLoader.java	(revision 11783)
@@ -20,58 +20,59 @@
 public class OsmTileLoader implements TileLoader {
 
-	protected TileLoaderListener listener;
+    protected TileLoaderListener listener;
 
-	public OsmTileLoader(TileLoaderListener listener) {
-		this.listener = listener;
-	}
+    public OsmTileLoader(TileLoaderListener listener) {
+        this.listener = listener;
+    }
 
-	public Runnable createTileLoaderJob(final TileSource source, final int tilex, final int tiley,
-			final int zoom) {
-		return new Runnable() {
+    public Runnable createTileLoaderJob(final TileSource source, final int tilex, final int tiley, final int zoom) {
+        return new Runnable() {
 
-			InputStream input = null;
+            InputStream input = null;
 
-			public void run() {
-				TileCache cache = listener.getTileCache();
-				Tile tile;
-				synchronized (cache) {
-					tile = cache.getTile(source, tilex, tiley, zoom);
-					if (tile == null || tile.isLoaded() || tile.loading)
-						return;
-					tile.loading = true;
-				}
-				try {
-					// Thread.sleep(500);
-					input = loadTileFromOsm(tile).getInputStream();
-					tile.loadImage(input);
-					tile.setLoaded(true);
-					listener.tileLoadingFinished(tile);
-					input.close();
-					input = null;
-				} catch (Exception e) {
-					if (input == null /* || !input.isStopped() */)
-						System.err.println("failed loading " + zoom + "/" + tilex + "/" + tiley
-								+ " " + e.getMessage());
-				} finally {
-					tile.loading = false;
-				}
-			}
+            public void run() {
+                TileCache cache = listener.getTileCache();
+                Tile tile;
+                synchronized (cache) {
+                    tile = cache.getTile(source, tilex, tiley, zoom);
+                    if (tile == null || tile.isLoaded() || tile.loading)
+                        return;
+                    tile.loading = true;
+                }
+                try {
+                    // Thread.sleep(500);
+                    input = loadTileFromOsm(tile).getInputStream();
+                    tile.loadImage(input);
+                    tile.setLoaded(true);
+                    listener.tileLoadingFinished(tile, true);
+                    input.close();
+                    input = null;
+                } catch (Exception e) {
+                    tile.setImage(Tile.ERROR_IMAGE);
+                    listener.tileLoadingFinished(tile, false);
+                    if (input == null)
+                        System.err.println("failed loading " + zoom + "/" + tilex + "/" + tiley + " " + e.getMessage());
+                } finally {
+                    tile.loading = false;
+                    tile.setLoaded(true);
+                }
+            }
 
-		};
-	}
+        };
+    }
 
-	protected HttpURLConnection loadTileFromOsm(Tile tile) throws IOException {
-		URL url;
-		url = new URL(tile.getUrl());
-		HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
-		urlConn.setReadTimeout(30000); // 30 seconds read
-		// timeout
-		return urlConn;
-	}
+    protected HttpURLConnection loadTileFromOsm(Tile tile) throws IOException {
+        URL url;
+        url = new URL(tile.getUrl());
+        HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
+        urlConn.setReadTimeout(30000); // 30 seconds read
+        // timeout
+        return urlConn;
+    }
 
-	@Override
-	public String toString() {
-		return getClass().getSimpleName();
-	}
-	
+    @Override
+    public String toString() {
+        return getClass().getSimpleName();
+    }
+
 }
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileSource.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileSource.java	(revision 11782)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileSource.java	(revision 11783)
@@ -14,4 +14,8 @@
 			return 18;
 		}
+
+		public int getMinZoom() {
+            return 0;
+        }
 
 		public String getTileUrl(int zoom, int tilex, int tiley) {
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Tile.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Tile.java	(revision 11782)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Tile.java	(revision 11783)
@@ -23,182 +23,197 @@
 public class Tile {
 
-	protected TileSource source;
-	protected int xtile;
-	protected int ytile;
-	protected int zoom;
-	protected BufferedImage image;
-	protected String key;
-	protected boolean loaded = false;
-	protected boolean loading = false;
-	public static final int SIZE = 256;
-
-	/**
-	 * Creates a tile with empty image.
-	 * 
-	 * @param source
-	 * @param xtile
-	 * @param ytile
-	 * @param zoom
-	 */
-	public Tile(TileSource source, int xtile, int ytile, int zoom) {
-		super();
-		this.source = source;
-		this.xtile = xtile;
-		this.ytile = ytile;
-		this.zoom = zoom;
-		this.image = null;
-		this.key = getTileKey(source, xtile, ytile, zoom);
-	}
-
-	public Tile(TileSource source, int xtile, int ytile, int zoom, BufferedImage image) {
-		this(source, xtile, ytile, zoom);
-		this.image = image;
-	}
-
-	/**
-	 * Tries to get tiles of a lower or higher zoom level (one or two level
-	 * difference) from cache and use it as a placeholder until the tile has
-	 * been loaded.
-	 */
-	public void loadPlaceholderFromCache(TileCache cache) {
-		BufferedImage tmpImage = new BufferedImage(SIZE, SIZE, BufferedImage.TYPE_INT_RGB);
-		Graphics2D g = (Graphics2D) tmpImage.getGraphics();
-		// g.drawImage(image, 0, 0, null);
-		for (int zoomDiff = 1; zoomDiff < 5; zoomDiff++) {
-			// first we check if there are already the 2^x tiles
-			// of a higher detail level
-			int zoom_high = zoom + zoomDiff;
-			if (zoomDiff < 3 && zoom_high <= JMapViewer.MAX_ZOOM) {
-				int factor = 1 << zoomDiff;
-				int xtile_high = xtile << zoomDiff;
-				int ytile_high = ytile << zoomDiff;
-				double scale = 1.0 / factor;
-				g.setTransform(AffineTransform.getScaleInstance(scale, scale));
-				int paintedTileCount = 0;
-				for (int x = 0; x < factor; x++) {
-					for (int y = 0; y < factor; y++) {
-						Tile tile =
-								cache.getTile(source, xtile_high + x, ytile_high + y, zoom_high);
-						if (tile != null && tile.isLoaded()) {
-							paintedTileCount++;
-							tile.paint(g, x * SIZE, y * SIZE);
-						}
-					}
-				}
-				if (paintedTileCount == factor * factor) {
-					image = tmpImage;
-					return;
-				}
-			}
-
-			int zoom_low = zoom - zoomDiff;
-			if (zoom_low >= JMapViewer.MIN_ZOOM) {
-				int xtile_low = xtile >> zoomDiff;
-				int ytile_low = ytile >> zoomDiff;
-				int factor = (1 << zoomDiff);
-				double scale = (double) factor;
-				AffineTransform at = new AffineTransform();
-				int translate_x = (xtile % factor) * SIZE;
-				int translate_y = (ytile % factor) * SIZE;
-				at.setTransform(scale, 0, 0, scale, -translate_x, -translate_y);
-				g.setTransform(at);
-				Tile tile = cache.getTile(source, xtile_low, ytile_low, zoom_low);
-				if (tile != null && tile.isLoaded()) {
-					tile.paint(g, 0, 0);
-					image = tmpImage;
-					return;
-				}
-			}
-		}
-	}
-
-	public TileSource getSource() {
-		return source;
-	}
-
-	/**
-	 * @return tile number on the x axis of this tile
-	 */
-	public int getXtile() {
-		return xtile;
-	}
-
-	/**
-	 * @return tile number on the y axis of this tile
-	 */
-	public int getYtile() {
-		return ytile;
-	}
-
-	/**
-	 * @return zoom level of this tile
-	 */
-	public int getZoom() {
-		return zoom;
-	}
-
-	public BufferedImage getImage() {
-		return image;
-	}
-
-	public void setImage(BufferedImage image) {
-		this.image = image;
-	}
-
-	public void loadImage(InputStream input) throws IOException {
-		image = ImageIO.read(input);
-	}
-
-	/**
-	 * @return key that identifies a tile
-	 */
-	public String getKey() {
-		return key;
-	}
-
-	public boolean isLoaded() {
-		return loaded;
-	}
-
-	public void setLoaded(boolean loaded) {
-		this.loaded = loaded;
-	}
-
-	public String getUrl() {
-		return source.getTileUrl(zoom, xtile, ytile);
-	}
-
-	/**
-	 * Paints the tile-image on the {@link Graphics} <code>g</code> at the
-	 * position <code>x</code>/<code>y</code>.
-	 * 
-	 * @param g
-	 * @param x
-	 *            x-coordinate in <code>g</code>
-	 * @param y
-	 *            y-coordinate in <code>g</code>
-	 */
-	public void paint(Graphics g, int x, int y) {
-		if (image == null)
-			return;
-		g.drawImage(image, x, y, null);
-	}
-
-	@Override
-	public String toString() {
-		return "Tile " + key;
-	}
-
-	@Override
-	public boolean equals(Object obj) {
-		if (!(obj instanceof Tile))
-			return false;
-		Tile tile = (Tile) obj;
-		return (xtile == tile.xtile) && (ytile == tile.ytile) && (zoom == tile.zoom);
-	}
-
-	public static String getTileKey(TileSource source, int xtile, int ytile, int zoom) {
-		return zoom + "/" + xtile + "/" + ytile + "@" + source.getName();
-	}
+    /**
+     * Hourglass image that is displayed until a map tile has been loaded
+     */
+    public static BufferedImage LOADING_IMAGE;
+    public static BufferedImage ERROR_IMAGE;
+
+    static {
+        try {
+            LOADING_IMAGE = ImageIO.read(JMapViewer.class.getResourceAsStream("images/hourglass.png"));
+            ERROR_IMAGE = ImageIO.read(JMapViewer.class.getResourceAsStream("images/error.png"));
+        } catch (Exception e1) {
+            LOADING_IMAGE = null;
+            ERROR_IMAGE = null;
+        }
+    }
+
+    protected TileSource source;
+    protected int xtile;
+    protected int ytile;
+    protected int zoom;
+    protected BufferedImage image;
+    protected String key;
+    protected boolean loaded = false;
+    protected boolean loading = false;
+    public static final int SIZE = 256;
+
+    /**
+     * Creates a tile with empty image.
+     * 
+     * @param source
+     * @param xtile
+     * @param ytile
+     * @param zoom
+     */
+    public Tile(TileSource source, int xtile, int ytile, int zoom) {
+        super();
+        this.source = source;
+        this.xtile = xtile;
+        this.ytile = ytile;
+        this.zoom = zoom;
+        this.image = LOADING_IMAGE;
+        this.key = getTileKey(source, xtile, ytile, zoom);
+    }
+
+    public Tile(TileSource source, int xtile, int ytile, int zoom, BufferedImage image) {
+        this(source, xtile, ytile, zoom);
+        this.image = image;
+    }
+
+    /**
+     * Tries to get tiles of a lower or higher zoom level (one or two level
+     * difference) from cache and use it as a placeholder until the tile has
+     * been loaded.
+     */
+    public void loadPlaceholderFromCache(TileCache cache) {
+        BufferedImage tmpImage = new BufferedImage(SIZE, SIZE, BufferedImage.TYPE_INT_RGB);
+        Graphics2D g = (Graphics2D) tmpImage.getGraphics();
+        // g.drawImage(image, 0, 0, null);
+        for (int zoomDiff = 1; zoomDiff < 5; zoomDiff++) {
+            // first we check if there are already the 2^x tiles
+            // of a higher detail level
+            int zoom_high = zoom + zoomDiff;
+            if (zoomDiff < 3 && zoom_high <= JMapViewer.MAX_ZOOM) {
+                int factor = 1 << zoomDiff;
+                int xtile_high = xtile << zoomDiff;
+                int ytile_high = ytile << zoomDiff;
+                double scale = 1.0 / factor;
+                g.setTransform(AffineTransform.getScaleInstance(scale, scale));
+                int paintedTileCount = 0;
+                for (int x = 0; x < factor; x++) {
+                    for (int y = 0; y < factor; y++) {
+                        Tile tile = cache.getTile(source, xtile_high + x, ytile_high + y, zoom_high);
+                        if (tile != null && tile.isLoaded()) {
+                            paintedTileCount++;
+                            tile.paint(g, x * SIZE, y * SIZE);
+                        }
+                    }
+                }
+                if (paintedTileCount == factor * factor) {
+                    image = tmpImage;
+                    return;
+                }
+            }
+
+            int zoom_low = zoom - zoomDiff;
+            if (zoom_low >= JMapViewer.MIN_ZOOM) {
+                int xtile_low = xtile >> zoomDiff;
+                int ytile_low = ytile >> zoomDiff;
+                int factor = (1 << zoomDiff);
+                double scale = (double) factor;
+                AffineTransform at = new AffineTransform();
+                int translate_x = (xtile % factor) * SIZE;
+                int translate_y = (ytile % factor) * SIZE;
+                at.setTransform(scale, 0, 0, scale, -translate_x, -translate_y);
+                g.setTransform(at);
+                Tile tile = cache.getTile(source, xtile_low, ytile_low, zoom_low);
+                if (tile != null && tile.isLoaded()) {
+                    tile.paint(g, 0, 0);
+                    image = tmpImage;
+                    return;
+                }
+            }
+        }
+    }
+
+    public TileSource getSource() {
+        return source;
+    }
+
+    /**
+     * @return tile number on the x axis of this tile
+     */
+    public int getXtile() {
+        return xtile;
+    }
+
+    /**
+     * @return tile number on the y axis of this tile
+     */
+    public int getYtile() {
+        return ytile;
+    }
+
+    /**
+     * @return zoom level of this tile
+     */
+    public int getZoom() {
+        return zoom;
+    }
+
+    public BufferedImage getImage() {
+        return image;
+    }
+
+    public void setImage(BufferedImage image) {
+        this.image = image;
+    }
+
+    public void loadImage(InputStream input) throws IOException {
+        image = ImageIO.read(input);
+    }
+
+    /**
+     * @return key that identifies a tile
+     */
+    public String getKey() {
+        return key;
+    }
+
+    public boolean isLoaded() {
+        return loaded;
+    }
+
+    public void setLoaded(boolean loaded) {
+        this.loaded = loaded;
+    }
+
+    public String getUrl() {
+        return source.getTileUrl(zoom, xtile, ytile);
+    }
+
+    /**
+     * Paints the tile-image on the {@link Graphics} <code>g</code> at the
+     * position <code>x</code>/<code>y</code>.
+     * 
+     * @param g
+     * @param x
+     *            x-coordinate in <code>g</code>
+     * @param y
+     *            y-coordinate in <code>g</code>
+     */
+    public void paint(Graphics g, int x, int y) {
+        if (image == null)
+            return;
+        g.drawImage(image, x, y, null);
+    }
+
+    @Override
+    public String toString() {
+        return "Tile " + key;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof Tile))
+            return false;
+        Tile tile = (Tile) obj;
+        return (xtile == tile.xtile) && (ytile == tile.ytile) && (zoom == tile.zoom);
+    }
+
+    public static String getTileKey(TileSource source, int xtile, int ytile, int zoom) {
+        return zoom + "/" + xtile + "/" + ytile + "@" + source.getName();
+    }
 
 }
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileLoaderListener.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileLoaderListener.java	(revision 11782)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileLoaderListener.java	(revision 11783)
@@ -13,5 +13,5 @@
 	 * @param tile
 	 */
-	public void tileLoadingFinished(Tile tile);
+	public void tileLoadingFinished(Tile tile, boolean success);
 
 	public TileCache getTileCache();
Index: /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java
===================================================================
--- /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java	(revision 11782)
+++ /applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java	(revision 11783)
@@ -11,69 +11,78 @@
 public interface TileSource {
 
-	/**
-	 * Specifies the different mechanisms for detecting updated tiles
-	 * respectively only download newer tiles than those stored locally.
-	 * 
-	 * <ul>
-	 * <li>{@link #IfNoneMatch} Server provides ETag header entry for all tiles
-	 * and <b>supports</b> conditional download via <code>If-None-Match</code>
-	 * header entry.</li>
-	 * <li>{@link #ETag} Server provides ETag header entry for all tiles but
-	 * <b>does not support</b> conditional download via
-	 * <code>If-None-Match</code> header entry.</li>
-	 * <li>{@link #IfModifiedSince} Server provides Last-Modified header entry
-	 * for all tiles and <b>supports</b> conditional download via
-	 * <code>If-Modified-Since</code> header entry.</li>
-	 * <li>{@link #LastModified} Server provides Last-Modified header entry for
-	 * all tiles but <b>does not support</b> conditional download via
-	 * <code>If-Modified-Since</code> header entry.</li>
-	 * <li>{@link #None} The server does not support any of the listed
-	 * mechanisms.</li>
-	 * </ul>
-	 * 
-	 */
-	public enum TileUpdate {
-		IfNoneMatch, ETag, IfModifiedSince, LastModified, None
-	};
+    /**
+     * Specifies the different mechanisms for detecting updated tiles
+     * respectively only download newer tiles than those stored locally.
+     * 
+     * <ul>
+     * <li>{@link #IfNoneMatch} Server provides ETag header entry for all tiles
+     * and <b>supports</b> conditional download via <code>If-None-Match</code>
+     * header entry.</li>
+     * <li>{@link #ETag} Server provides ETag header entry for all tiles but
+     * <b>does not support</b> conditional download via
+     * <code>If-None-Match</code> header entry.</li>
+     * <li>{@link #IfModifiedSince} Server provides Last-Modified header entry
+     * for all tiles and <b>supports</b> conditional download via
+     * <code>If-Modified-Since</code> header entry.</li>
+     * <li>{@link #LastModified} Server provides Last-Modified header entry for
+     * all tiles but <b>does not support</b> conditional download via
+     * <code>If-Modified-Since</code> header entry.</li>
+     * <li>{@link #None} The server does not support any of the listed
+     * mechanisms.</li>
+     * </ul>
+     * 
+     */
+    public enum TileUpdate {
+        IfNoneMatch, ETag, IfModifiedSince, LastModified, None
+    };
 
-	/**
-	 * Specifies the maximum zoom value. The number of zoom levels is [0..
-	 * {@link #getMaxZoom()}].
-	 * 
-	 * @return maximum zoom value that has to be smaller or equal to
-	 *         {@link JMapViewer#MAX_ZOOM}
-	 */
-	public int getMaxZoom();
+    /**
+     * Specifies the maximum zoom value. The number of zoom levels is [0..
+     * {@link #getMaxZoom()}].
+     * 
+     * @return maximum zoom value that has to be smaller or equal to
+     *         {@link JMapViewer#MAX_ZOOM}
+     */
+    public int getMaxZoom();
 
-	/**
-	 * @return The supported tile update mechanism
-	 * @see TileUpdate
-	 */
-	public TileUpdate getTileUpdate();
+    /**
+     * Specifies the minimum zoom value. This value is usually 0. 
+     * Only for maps that cover a certain region up to a limited zoom level 
+     * this method should return a value different than 0.  
+     * 
+     * @return minimum zoom value - usually 0
+     */
+    public int getMinZoom();
 
-	/**
-	 * A tile layer name has to be unique and has to consist only of characters
-	 * valid for filenames.
-	 * 
-	 * @return Name of the tile layer
-	 */
-	public String getName();
+    /**
+     * @return The supported tile update mechanism
+     * @see TileUpdate
+     */
+    public TileUpdate getTileUpdate();
 
-	/**
-	 * Constructs the tile url.
-	 * 
-	 * @param zoom
-	 * @param tilex
-	 * @param tiley
-	 * @return fully qualified url for downloading the specified tile image
-	 */
-	public String getTileUrl(int zoom, int tilex, int tiley);
+    /**
+     * A tile layer name has to be unique and has to consist only of characters
+     * valid for filenames.
+     * 
+     * @return Name of the tile layer
+     */
+    public String getName();
 
-	/**
-	 * Specifies the tile image type. For tiles rendered by Mapnik or
-	 * Osmarenderer this is usually <code>"png"</code>.
-	 * 
-	 * @return file extension of the tile image type
-	 */
-	public String getTileType();
+    /**
+     * Constructs the tile url.
+     * 
+     * @param zoom
+     * @param tilex
+     * @param tiley
+     * @return fully qualified url for downloading the specified tile image
+     */
+    public String getTileUrl(int zoom, int tilex, int tiley);
+
+    /**
+     * Specifies the tile image type. For tiles rendered by Mapnik or
+     * Osmarenderer this is usually <code>"png"</code>.
+     * 
+     * @return file extension of the tile image type
+     */
+    public String getTileType();
 }
